¿Por qué MySQL no siempre usa la fusión de índices aquí?

Considere esta tabla:

CREATE TABLE `Alarms` (
  `AlarmId` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `DeviceId` BINARY(16) NOT NULL,
  `Code` BIGINT(20) UNSIGNED NOT NULL,
  `Ended` TINYINT(1) NOT NULL DEFAULT '0',
  `NaturalEnd` TINYINT(1) NOT NULL DEFAULT '0',
  `Pinned` TINYINT(1) NOT NULL DEFAULT '0',
  `Acknowledged` TINYINT(1) NOT NULL DEFAULT '0',
  `StartedAt` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
  `EndedAt` TIMESTAMP NULL DEFAULT NULL,
  `MarkedForDeletion` TINYINT(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`AlarmId`),
  KEY `Key1` (`Ended`,`Acknowledged`),
  KEY `Key2` (`Pinned`),
  KEY `Key3` (`DeviceId`,`Pinned`),
  KEY `Key4` (`DeviceId`,`StartedAt`,`EndedAt`),
  KEY `Key5` (`DeviceId`,`Ended`,`EndedAt`),
  KEY `Key6` (`MarkedForDeletion`)
) ENGINE=INNODB;

Y, para esta prueba, llénela así:

-- Populate some dummy data; 500 alarms for each
-- of 1000 one-second periods
SET @testDevice = UNHEX('00030000000000000000000000000000');

DROP PROCEDURE IF EXISTS `injectAlarms`;
DELIMITER ;;
CREATE PROCEDURE injectAlarms()
BEGIN
    SET @fromdate  = '2018-02-18 00:00:00';
    SET @numdates  = 1000;
    SET @todate    = DATE_ADD(@fromdate, INTERVAL @numdates SECOND);

    -- Create table of alarm codes to join on
    DROP TABLE IF EXISTS `__codes`;
    CREATE TEMPORARY TABLE `__codes` (
        `Code` BIGINT NOT NULL PRIMARY KEY
    );

    SET @startcode = 0;
    SET @endcode   = 499;

    REPEAT
       INSERT INTO `__codes` VALUES(@startcode);
       SET @startcode = @startcode + 1;
    UNTIL @startcode > @endcode END REPEAT;

    -- Add an alarm for each code, for each second in range
    REPEAT
        INSERT INTO `Alarms`
            (`DeviceId`, `Code`, `Ended`, `NaturalEnd`, `Pinned`, `Acknowledged`, `StartedAt`, `EndedAt`)
            SELECT
                @testDevice,
                `Code`,
                TRUE, FALSE, FALSE, FALSE,
                @fromdate, @fromdate
            FROM `__codes`;

        SET @fromdate = DATE_ADD(@fromdate, INTERVAL 1 SECOND);
    UNTIL @fromdate > @todate END REPEAT;
END;;
DELIMITER ;

CALL injectAlarms();

Ahora, para algunos conjuntos de datos, la siguiente consulta funciona bastante bien:

SELECT * FROM `Alarms`
WHERE
   ((`Alarms`.`Ended` = FALSE AND `Alarms`.`Acknowledged` = FALSE) OR `Alarms`.`Pinned` = TRUE) AND
   `MarkedForDeletion` = FALSE AND
   `DeviceId` = @testDevice
;

Esto se debe a que MariaDB es lo suficientemente inteligente como para usar combinaciones de índices, por ejemplo:

id    select_type    table    type         possible_keys                 
1     SIMPLE         Alarms   index_merge  Key1,Key2,Key3,Key4,Key5,Key6 

key             key_len  ref     rows     Extra
Key1,Key2,Key3  2,1,17   (NULL)  2        Using union(Key1,intersect(Key2,Key3)); Using where

Sin embargo, si uso el conjunto de datos tal como se detalla en el procedimiento anterior y volteo la consulta un poco (que es otra vista que necesito, pero en este caso devolverá muchas más filas):

SELECT * FROM `Alarms`
WHERE
  ((`Alarms`.`Ended` = TRUE OR `Alarms`.`Acknowledged` = TRUE) AND `Alarms`.`Pinned` = FALSE) AND
   `MarkedForDeletion` = FALSE AND
   `DeviceId` = @testDevice
;

... no lo hace:

id    select_type    table    type   possible_keys
1     SIMPLE         Alarms   ref    Key1,Key2,Key3,Key4,Key5,Key6

key   key_len  ref     rows     Extra
Key2  1        const  144706    Using where

Prefiero que las fusiones de índices sucedan más a menudo. Como es, dado elref=const, este plan de consulta no parece demasiado aterrador ... sin embargo, la consulta tarda casi un segundo en ejecutarse. Eso en sí mismo no es el fin del mundo, pero la naturaleza escasamente escasa de mi diseño se muestra al intentar una consulta más exótica, que requiere unmuy largo tiempo:

-- Create a temporary table that we'll join against in a mo
DROP TABLE IF EXISTS `_ranges`;
CREATE TEMPORARY TABLE `_ranges` (
    `Start` TIMESTAMP NOT NULL DEFAULT 0,
    `End`   TIMESTAMP NOT NULL DEFAULT 0,
    PRIMARY KEY(`Start`, `End`)
);

-- Populate it (in reality this is performed by my application layer)
SET @endtime = 1518992216;
SET @starttime = @endtime - 86400;
SET @inter = 900;
DROP PROCEDURE IF EXISTS `populateRanges`;
DELIMITER ;;
CREATE PROCEDURE populateRanges()
BEGIN
REPEAT
    INSERT IGNORE INTO `_ranges` VALUES(FROM_UNIXTIME(@starttime),FROM_UNIXTIME(@starttime + @inter));
    SET @starttime = @starttime + @inter;
UNTIL @starttime > @endtime END REPEAT;
END;;
DELIMITER ;
CALL populateRanges();

-- Actual query
SELECT UNIX_TIMESTAMP(`_ranges`.`Start`) AS `Start_TS`,
COUNT(`Alarms`.`AlarmId`) AS `n`
FROM `_ranges`
LEFT JOIN `Alarms`
ON `Alarms`.`StartedAt` < `_ranges`.`End`
  AND (`Alarms`.`EndedAt` IS NULL OR `Alarms`.`EndedAt` >= `_ranges`.`Start`)

  AND ((`Alarms`.`EndedAt` IS NULL AND `Alarms`.`Acknowledged` = FALSE) OR `Alarms`.`Pinned` = TRUE)
-- Again, the above condition is sometimes replaced by:
-- AND ((`Alarms`.`EndedAt` IS NOT NULL OR `Alarms`.`Acknowledged` = TRUE) AND `Alarms`.`Pinned` = FALSE)

 AND `DeviceId` = @testDevice
 AND `MarkedForDeletion` = FALSE
 GROUP BY `_ranges`.`Start`

(Se supone que esta consulta debe reunir una lista de recuentos por segmento de tiempo, cada conteo indica cuántas alarmas '[StartedAt, EndedAt] se cruzan con ese segmento de tiempo. El resultado completa un gráfico de líneas).

Nuevamente, cuando diseñé estas tablas y no había muchas filas en ellas, las fusiones de índice parecían hacer que todo funcionara. Pero ahora no es así: con el conjunto de datos como se indica eninjectAlarms(), esto toma40 segundos ¡completar!

Me di cuenta de esto al agregar elMarkedForDeletion columna y realizando algunas de mis primeras pruebas de escala de gran conjunto de datos. Esta es la razón por la que mi elección de índices no hace gran cosa por la presencia deMarkedForDeletion, aunque los resultados descritos anteriormente son los mismos si eliminoAND MarkedForDeletion = FALSE de mis consultas; sin embargo, he mantenido la condición, ya que finalmente la necesitaré para estar allí.

He probado algunosUSE INDEX/FORCE INDEX combinaciones, pero nunca parece utilizar la combinación de índice como resultado.

¿Qué índices puedo definir para que esta tabla se comporte rápidamente en los casos dados? ¿O cómo puedo reestructurar mis consultas para lograr el mismo objetivo?

(Los planes de consulta anteriores se obtuvieron en MariaDB 5.5.56 / CentOS 7, pero la solución también debe funcionar en MySQL 5.1.73 / CentOS 6.)

Respuestas a la pregunta(1)

Su respuesta a la pregunta