MySQL 8.0 执行 COUNT() 很慢原因分析
MySQL 8.0 执行 COUNT()很慢原因分析
1.1 问题描述
线上 MySQL8.0.32 环境在执行 SELECT COUNT (1) FROM t0 获取表行数很慢,同样场景下该 SQL 在 MySQL5.7 环境很快就能拿到结果
1.2 问题复现
测试版本:8.0.25 MySQL Community Server - GPL 和 5.7.21-log MySQL Community Server (GPL)
1.2.1 复现准备
创建表并初始化数据
修改配置文件,设置 innodb_buffer_pool_load_at_startup=OFF
重启数据库,确保下次查询时从磁盘加载,systemctl restart mysql3307
1.2.2 8.0.25 的测试结果
执行计划显示走的是二级索引
执行很慢,需要 8 秒
执行期间的 top 显示 CPU 冲高到 200%+,磁盘 I/O 也很高,说明扫描了聚簇索引树,启用了并行查询
CPU 监控
磁盘监控
1.2.3 5.7.21 的测试结果
执行计划显示走的是二级索引
执行很快,0.81 秒就执行完成
执行期间的 top 显示 CPU 只有 20%+,磁盘 I/O 也很低,说明根本没通过聚簇索引
CPU 监控
磁盘监控
1.2.4 复现结论
通过以上 8.0.25 和 5.7.21 的对比测试,我们发现尽管两者 explain 的执行计划中都声明采用的是二级索引 idx_i1 ,但是实际执行中,8.0.25 还是用的聚簇索引,资源占用高并且执行慢;而 5.7.21 真实的走二级索引,资源占用低并且执行很快
这带来了两个缺陷:
实际的执行计划和 explain 的结果不一致,会给 SQL 排查带来干扰。需要将 explain 的 key 列改成 PRIMARY
采用的索引不是最优,导致执行得很慢
2. 问题分析
在 8.0.17 版本中引入了 records_from_index(ha_rows *num_rows, uint) 函数,该函数忽略了上层传入的 index 参数,直接调用 InnoBase::records()让 InnoDB 自己计算行数并返回,并且强制写了走主键索引的逻辑,导致的结果是无法选择最小索引树来实现遍历,实际执行中只能用到主键索引,即使 SQL 中加了使用二级索引的 hint 也不行。当然,等二级索引支持并行查询后就可以在调用 records_from_index 时实际用到传入的 index,但是在 8.0.17 至 8.0.36 之间的版本执行 select count 都会造成很大的执行代价,并且执行计划还会误导 DBA 以为执行器是用二级索引树执行的扫描。
MySQL 8.0.37 中做了优化,解决方式是在 sql/handler.cc 中添加 handler::records_from_index(ha_rows *num_rows, uint index) 使用具体的二级索引来执行查询,详细结果见 https://gitee.com/mirrors/mysql-server/commit/22768a0f830c5be769bea0c464a8721ec266beef
同时在 MySQL 8.0.37 的 changelog https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-37.html 中有这样的描述:
InnoDB: MySQL no longer ignores the optimizer hint to use a secondary index scan, which instead forced a clustered (parallel) index scan. (Bug #100597, Bug #112767, Bug #31791868, Bug #35952353)
因此,从 MySQL 8.0.37 及以后的版本中,不再强制使用聚集索引的并行查询,而是遵循 hint/优化器 的建议可以使用二级索引扫描。
3. 解决方案和优化建议
最直接的建议是升级到 MySQL 8.0.37,但是也要注意不要使用 MySQL 8.0.38/8.4.1/9.0.0 版本,因为这三个版本中存在致命 Bug #36808732 (当创建表超过 8000 以后启动失败),不过这三个版本已经下载不到了,只是 tag 还保留着。
4. 参考文章
MySQL 8.0.37 的发布文档 https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-37.html
INDEX hint does not affect count(*) execution https://bugs.mysql.com/bug.php?id=100597
The performance of version 8.0 when using count(1) is significantly lower compar https://bugs.mysql.com/bug.php?id=111969
评论