表:
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`),
KEY `KeyB` (`MarkedForDeletion`,`DeviceId`,`StartedAt`,`EndedAt`,`Acknowledged`,`Pinned`)
) ENGINE=INNODB;
目前它有大约300万行。
查询:
SELECT
COUNT(`AlarmId`) AS `n`
FROM `Alarms`
WHERE `StartedAt` < FROM_UNIXTIME(1519101900)
AND (`EndedAt` IS NULL OR `EndedAt` > FROM_UNIXTIME(1519101900))
AND `DeviceId` = UNHEX('00030000000000000000000000000000')
AND `MarkedForDeletion` = FALSE
AND (
(`Alarms`.`EndedAt` IS NULL AND `Alarms`.`Acknowledged` = FALSE)
OR ( `Alarms`.`EndedAt` IS NOT NULL AND `Alarms`.`Pinned` = TRUE)
)
查询计划:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE Alarms range Key2,Key3,Key4,Key5,Key6,KeyB KeyB 21 1574778 Using where; Using index
经过时间:1,763,222μs
在这种特殊情况下,查询(正确)甚至不匹配很多行(结果为n = 2
)。
从我使用索引合并学到的东西(虽然我还没有那么正确),我尝试重新组织条件(原始是由一些C ++生成的,基于输入条件,因此奇怪的运算符分布):
SELECT COUNT(`AlarmId`) AS `n`
FROM `Alarms`
WHERE
(
`EndedAt` IS NULL
AND `Acknowledged` = FALSE
AND `StartedAt` < FROM_UNIXTIME(1519101900)
AND `MarkedForDeletion` = FALSE
AND `DeviceId` = UNHEX('00030000000000000000000000000000')
) OR (
`EndedAt` > FROM_UNIXTIME(1519101900)
AND `Pinned` = TRUE
AND `StartedAt` < FROM_UNIXTIME(1519101900)
AND `MarkedForDeletion` = FALSE
AND `DeviceId` = UNHEX('00030000000000000000000000000000')
);
......但结果是一样的。
那为什么需要这么长时间?如何修改它/索引以使其立即生效?
答案 0 :(得分:1)
我认为问题在于我试图在索引中途使用范围条件。
我在上面添加了一个键:
(`MarkedForDeletion`,`DeviceId`,`Acknowledged`,`Ended`,`StartedAt`)
然后将查询重写为:
SELECT COUNT(`AlarmId`) AS `n`
FROM `Alarms`
WHERE
(
`Ended` = FALSE
AND `Acknowledged` = FALSE
AND `StartedAt` < FROM_UNIXTIME(1519101900)
AND `MarkedForDeletion` = FALSE
AND `DeviceId` = UNHEX('00030000000000000000000000000000')
) OR (
`EndedAt` > FROM_UNIXTIME(1519101900)
AND `Pinned` = TRUE
AND `StartedAt` < FROM_UNIXTIME(1519101900)
AND `MarkedForDeletion` = FALSE
AND `DeviceId` = UNHEX('00030000000000000000000000000000')
);
现在我得到索引合并,查询是即时的。
答案 1 :(得分:1)
OR
很难优化。要避免这两种情况,请将OR
转为UNION
。每个SELECT
都可以使用不同的索引。因此,为每个人构建一个最佳INDEX
。
实际上,由于您只是COUNT
,所以您也可以评估两个单独的计数并添加它们。
SELECT ( SELECT COUNT(*)
FROM `Alarms`
WHERE `EndedAt` IS NULL
AND `Acknowledged` = FALSE
AND `StartedAt` < FROM_UNIXTIME(1519101900)
AND `MarkedForDeletion` = FALSE
AND `DeviceId` = UNHEX('00030000000000000000000000000000' )
) +
( SELECT COUNT(*)
FROM `Alarms`
WHERE `EndedAt` > FROM_UNIXTIME(1519101900)
AND `Pinned` = TRUE
AND `StartedAt` < FROM_UNIXTIME(1519101900)
AND `MarkedForDeletion` = FALSE
AND `DeviceId` = UNHEX('00030000000000000000000000000000')
) AS `n`;
INDEX(DeviceId, Acknowledged, MarkedForDeletion, EndedAt, StartedAt) -- for first
INDEX(DeviceId, Pinned, MarkedForDeletion, EndedAt, StartedAt) -- for second
INDEX(DeviceId, Pinned, MarkedForDeletion, StartedAt, EndedAt) -- for second
嗯,如果有重叠,它就不会起作用。所以,让我们回到UNION
模式:
SELECT COUNT(*) AS `n`
FROM
(
( SELECT AlarmId
FROM `Alarms`
WHERE `EndedAt` IS NULL
AND `Acknowledged` = FALSE
AND `StartedAt` < FROM_UNIXTIME(1519101900)
AND `MarkedForDeletion` = FALSE
AND `DeviceId` = UNHEX('00030000000000000000000000000000')
)
UNION DISTINCT
( SELECT AlarmId
FROM `Alarms`
WHERE `EndedAt` > FROM_UNIXTIME(1519101900)
AND `Pinned` = TRUE
AND `StartedAt` < FROM_UNIXTIME(1519101900)
AND `MarkedForDeletion` = FALSE
AND `DeviceId` = UNHEX('00030000000000000000000000000000')
)
);
再次添加这些索引。
每个INDEX
中的前几列可以按任何顺序排列,因为它们是使用=
(或IS NULL
)进行测试的。最后一两个是&#34;范围&#34;试验。只有第一个范围用于过滤,但我包含了另一个列,因此索引将是&#34;覆盖&#34;。
我的配方可能优于&#34;索引合并&#34;。