sne*_*l29 7 innodb mariadb execution-plan
我们有一个包含超过 1TB 数据的 mariadb 表(故事),定期运行一个查询来获取最近添加的行以在其他地方建立索引。
innodb_version: 5.6.36-82.1
version : 10.1.26-MariaDB-0+deb9u1
Run Code Online (Sandbox Code Playgroud)
当查询优化器决定使用二级索引进行范围遍历(以 1000 为单位)时,查询工作正常
explain extended SELECT stories.item_guid
FROM `stories`
WHERE (updated_at >= '2018-09-21 15:00:00')
AND (updated_at <= '2018-09-22 05:30:00')
ORDER BY `stories`.`id` ASC
LIMIT 1000;
+------+-------------+---------+-------+-----------------------------+-----------------------------+---------+------+--------+----------+---------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+-------------+---------+-------+-----------------------------+-----------------------------+---------+------+--------+----------+---------------------------------------+
| 1 | SIMPLE | stories | range | index_stories_on_updated_at | index_stories_on_updated_at | 5 | NULL | 192912 | 100.00 | Using index condition; Using filesort |
+------+-------------+---------+-------+-----------------------------+-----------------------------+---------+------+--------+----------+---------------------------------------+
1 row in set, 1 warning (0.00 sec)
Run Code Online (Sandbox Code Playgroud)
但偶尔,即使数据集有微小的差异(注意上面查询的第二个时间戳差异,值得一提的是整个表保存了几年的数据,保存了几千万行)决定使用主键索引
explain extended SELECT stories.item_guid
FROM `stories`
WHERE (updated_at >= '2018-09-21 15:00:00')
AND (updated_at <= '2018-09-22 06:30:00')
ORDER BY `stories`.`id` ASC
LIMIT 1000;
+------+-------------+---------+-------+-----------------------------+---------+---------+------+--------+----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+-------------+---------+-------+-----------------------------+---------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | stories | index | index_stories_on_updated_at | PRIMARY | 8 | NULL | 240259 | 83.81 | Using where |
+------+-------------+---------+-------+-----------------------------+---------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
Run Code Online (Sandbox Code Playgroud)
导致它遍历整个主键索引(我猜是按顺序)然后过滤updated_at需要几个小时才能完成的字段。
该查询是由 ORM ActiveRecord 创建的,可能远非理想,作为一种变通解决方案,我们提出了几个手动制作的查询,ORDER BY stories.id移出,和/或使用 use/force 索引来避免 PK,因为我们是真正过滤我们的数据集updated_at。
我在这里有兴趣了解的是查询优化器如何/为什么选择该执行计划,我了解查询优化器使用索引/表统计信息来做出该决定,但在这种情况下,如果我对innodb工作原理的理解是正确的,这很明显,在不使用任何 PK id 进行过滤的情况下遍历一个巨大的 PK 是不理想的。
我基本上是想了解那个“坏”决定的来源,使用哪些统计数据或未知变量来结束好计划(经常选择的计划,而且速度要快几个数量级)
随时纠正我的任何假设,因为我绝对不是 DBA 专家,
提前致谢!
简短回答: 和WHERE正在ORDER BY向不同的方向牵引。优化器还不够智能,无法始终正确地决定拉动的方向。
长答案:
WHERE任何以 . 开头的 索引的好处updated_at。查看“100%”等内容。这样的索引可以快速找到所需的行,全部 192K。但是查询需要对 192K 行进行排序 ( ORDER BY) 才能到达LIMIT.
ORDER BY id的好处PRIMARY KEY。该索引允许查询按顺序获取所有行(从而避免排序),因此可以获取LIMIT(从而永远不会铲除超过 1000 行。
如果发现 1000 的值较小id,则运行速度会很快。如果所需的 1000 个在表中较晚,查询将运行缓慢。优化器无法预测(没有更多的智能和复杂性)。
update_at主要是跟踪吗id?如果是这样,那么任一索引都会导致本质上相同的块。但优化器既没有注意到这一点,也没有利用它。
可能的加速:
“覆盖”索引是一种包含所有所需列的索引。由于 PK 与数据“聚集”,因此PRIMARY KEY(id)是一种覆盖索引。但我们看到了情况可能有多糟糕。以下可能会更好:
INDEX(updated_at, -- first, to deal with the WHERE
id, item_guid) -- in either order
Run Code Online (Sandbox Code Playgroud)
该查询对 192K 行进行范围扫描的速度比需要在INDEX(updated_at)和 数据之间跳转 192K 次才能找到 的速度更快item_guid。排序不会得到改善。
唉,这加快了更快的查询速度。所以,让我重新思考一下。
分区?
哦,您可能已经找到了 的第五个用例PARTITIONing。六年前,我只有 4 个用例;我还没有找到新的用例。让我谈谈您的情况可能有何不同。
假设你用过PARTITION BY RANGE (TO_DAYS(updated_at))表上,设置了大约50个分区。(如果您需要清除“旧”数据,分区非常好。)
设置分区时,需要重新考虑所有索引。现在有哪些指标?我假设这些:
PRIMARY KEY(id),
INDEX(updated_at)
Run Code Online (Sandbox Code Playgroud)
就你而言,也许不会有太大改变:
PRIMARY KEY(id, updated_at),
INDEX(updated_at)
Run Code Online (Sandbox Code Playgroud)
您的查询会发生什么情况?
解决WHERE第一个问题不会有太大改变。是的,会有分区修剪,加上二级索引。速度将保持大致相同。
通过解决ORDER BY第一个问题,查询计划还可以缩减为一个(或两个)分区。id那么在那个/那些分区内按顺序扫描的速度将是原来的 50(或 25)倍。可能会增加排序,因为来自不同分区的行不会按顺序排列。
其他注意事项
我们能看到吗SHOW CREATE TABLE?1TB 表需要很多东西来帮助提高性能。如果您的所有数字都是INTs,那么可能有很多空间可以通过缩小到 等来恢复MEDIUMINT。标准化可能值得做。如果建立了索引,那一定会对插入GUID性能造成很大的负担。等等等等.
Ypercube 的
如果ORDER BY updated_at, id任务可以完成,那么就消除了使用缓慢解释计划的诱惑。并INDEX(updated_at, id, item_guid)成为最佳指标。并且分区变得毫无用处。
分页
不是通过 OFFSET,记住你离开的地方。这讨论了如何处理涉及多个单列的“剩余”。
| 归档时间: |
|
| 查看次数: |
944 次 |
| 最近记录: |