以下是我的表格。有些专栏没有必要复制,但这是我真实世界的实际表格,而且它很小,所以我发布了整个内容:
CREATE TABLE `Alarms` (
`AlarmId` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '32-bit ID',
`Code` BIGINT(20) UNSIGNED NOT NULL,
`GenerationTime` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
`ExpiryTime` TIMESTAMP NULL DEFAULT NULL,
`ExpiryCause` ENUM('natural','artificial') DEFAULT NULL,
`AckTime` TIMESTAMP NULL DEFAULT NULL,
`ClearTime` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`AlarmId`),
KEY `AlarmExpiry` (`ExpiryTime`),
KEY `AlarmLevels` (`AckTime`,`ClearTime`),
KEY `AlarmTime` (`GenerationTime`),
) ENGINE=INNODB;
现在,最低限度,如何重现我的问题。
以下使用AlarmTime
索引进行快速排序和LIMIT
:
SELECT
`AlarmId`
FROM
`Alarms`
ORDER BY
`GenerationTime` DESC
, `AlarmId` DESC
LIMIT
2055820, 20
说明:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE Alarms index (NULL) AlarmTime 4 (NULL) 2050259 Using index
以下内容并非如此。它缓慢地进行文件分析(几秒钟,行长约2米):
SELECT
`AlarmId`
, `Code`
FROM
`Alarms`
ORDER BY
`GenerationTime` DESC
, `AlarmId` DESC
LIMIT
2055820, 20
说明:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE Alarms ALL (NULL) (NULL) (NULL) (NULL) 2050259 Using filesort
我不明白为什么LIMIT
似乎阻止我能够选择任何不会成为桌上钥匙一部分的列。
这两个看似相关的手册页[1] [2]似乎没有提到这种情况。
我错过了哪些表索引的细微差别?
[1]:https://dev.mysql.com/doc/refman/5.7/en/order-by-optimization.html
[2]:https://dev.mysql.com/doc/refman/5.7/en/limit-optimization.html
答案 0 :(得分:2)
在对我的MySQL服务器进行一些测试后,我只能确认Sloan Thrasher在说什么......
优化器实际上考虑了所选列以及Order By列。添加以下索引是否需要。
ALTER TABLE alarms
ADD INDEX GenerationTime_AlarmId_Code (GenerationTime DESC, AlarmId DESC, Code);
<强>更新强>
我已经确认了您的数据集...以DESC顺序创建上述组合索引,产生最佳结果。从我们系统的2.278秒到.765秒。
作为旁注,如果您的订单依据中不需要AlarmId DESC
,
(例如,如果您有2个具有完全相同GenerationTime的AlarmId,则不必关心AlarmId首先在列表中显示)
我没有理由使用它。所以我说从Order by中删除它。制作稍快一些。 &#34; 0.749&#34;
另一方面,如果你需要按照相同的顺序将你的AlarmId列为总是列出,你还需要按确定性列(PK)分组,以确保它们以相同的顺序显示。见Here
如果确保使用和不使用LIMIT的相同行顺序很重要,请在ORDER BY子句中包含其他列以使订单具有确定性。
答案 1 :(得分:1)
首先,LIMIT 2055820, 20
糟糕且不切实际。有人坐在那里按UI上的[Next] 102,791次?!我对此表示怀疑。你真的有一个延伸到那么远的用户界面吗?您是否了解查询必须通过2,055,820条记录才能获得您想要的20条记录?
抱歉要严厉,但我讨厌错误的UI设计。
如前所述,INDEX(GenerationTime, AlarmId, Code)
是唯一可以添加以帮助查询的内容。它有助于SELECT
的两种变体。 (注意:DESC
在这种情况下不需要 。INDEX
中允许使用该语法,但在版本8.0之前将被忽略。)
如果确实确实需要分页,请参阅my article了解如何&#34;记住你离开的地方&#34;。
如果您的UI正在探测大型数据集,那么请考虑提供一种方法来选择(或输入)GenerationTime
的值作为此大流中的起点。
一种想法是将GenerationTime分解为大约50个相等的范围;让用户选择;然后在那里为他提供了50个细分。 然后扫描只有大约50页,每行20行 - 很多更易于管理。