MySQL如何基于成本制定执行计划
现在,我要用下面的SQL
执行计划是全表扫描。但只要把create_time条件中的5点改为6点就变为走索引了,并且走的是create_time索引而不是name_score联合索引:
该实验可以得到如下结论:
- MySQL选择索引,并非按照WHERE条件中列的顺序
- 即便列有索引,甚至有多个可能的索引方案,MySQL也可能根本不走索引
因为MySQL是根据成本判断的。虽然表的统计信息不完全准确,但足够用于策略的判断。
不过,有时会因为统计信息的不准确或成本估算问题,实际开销会和MySQL统计出来的差距较大,导致MySQL选择错误的索引或是直接全表扫描,这就需要人工干预,使用强制索引。
强制走name_score索引:
EXPLAIN SELECT * FROM person FORCE INDEX(name_score) WHERE NAME >'name84059' AND create_time>'2020-01-24 05:00:00'
MySQL会根据成本选择执行计划,通过EXPLAIN可以知道优化器最终会选择怎样的执行计划,但MySQL如何制定执行计划始终是一个黑盒。
有没有什么办法可以了解各种执行计划的成本,以及MySQL做出选择的依据?
MySQL 5.6及之后,可以使用optimizer trace查看优化器生成执行计划的整个过程。有了这个功能,我们不仅可以了解优化器的选择过程,更可以了解每一个执行环节的成本,然后依靠这些信息进一步优化查询。
- 打开optimizer_trace后
- 再执行SQL
- 就可以查询information_schema.OPTIMIZER_TRACE表查看执行计划了
- 最后可以关闭optimizer_trace
SET optimizer_trace="enabled=on"; SELECT * FROM person WHERE NAME >'name84059' AND create_time>'2020-01-24 05:00:00'; SELECT * FROM information_schema.OPTIMIZER_TRACE; SET optimizer_trace="enabled=off";
对于按照create_time>'2020-01-24 05:00:00’条件走全表扫描的SQL,来分析OPTIMIZER_TRACE的执行结果。
使用person_name_score_index对name84059<name条件进行索引扫描需扫描33918行,成本11872,所以未选择该方案
33918 = 查询二级索引的I/O成本和CPU成本 + 回表查询聚簇索引的I/O成本和CPU成本
使用person_create_time_index
进行索引扫描需要扫描35606行,成本是12462,也是因为成本未选择该方案
最终选择全表扫描作为执行计划。全表扫描100147条记录的成本是10103,小于其他方案。
把SQL中的create_time条件从05:00改为06:00,再次分析OPTIMIZER_TRACE。这次执行计划选择的是走person_create_time_index索引。因为是查询更晚时间的数据,走person_create_time_index索引需要扫描的行数从35606减少到了27218。这次走这个索引的成本9526.6小于全表扫描的10103,更小于走name_score索引的30435:
考虑到索引的维护代价、空间占用和查询时回表的代价,不能认为索引越多越好。索引一定是按需创建的,并且要尽可能确保足够轻量。
一旦创建了多字段的联合索引,我们要考虑尽可能利用索引本身完成数据查询,减少回表。
不能认为建了索引就一定有效,对于后缀的匹配查询、查询中不包含联合索引的第一列、查询条件涉及函数计算等无法使用索引。
即使SQL本身符合索引使用条件,MySQL也会通过评估各种查询方式的代价,来决定是否走索引,走哪个索引。
尝试通过索引进行SQL性能优化时,请一定通过执行计划或实际的效果来确认索引是否能有效改善性能问题,否则增加了索引不但没解决性能问题,还增加了数据库增删改的负担。
对EXPLAIN结果困惑的,还可以利用optimizer_trace查看详细的执行计划,各个索引的成本是多少,看看到底怎么挑选出来的最终方案。
参考
https://dev.mysql.com/doc/internals/en/optimizer-tracing.html