EXPLAIN显示走索引但查询仍慢,主因是索引扫描行数过多或回表开销大;需检查rows_examined_per_scan、filtered、覆盖索引、ORDER BY是否匹配索引、I/O等待、临时表落盘及缓冲池命中率。

为什么 EXPLAIN 显示走了索引,查询还是慢?
索引命中 ≠ 查询快。MySQL 确实用了索引(type 是 ref/range,key 有值),但实际执行仍卡顿,大概率是「索引扫描行数太多」或「回表开销大」。
比如一个 WHERE status = 1 AND create_time > '2024-01-01' 查询,即使 status 有索引,若该值占比 80%,MySQL 仍要扫描 80% 的索引节点;如果没覆盖索引,还得逐行回主键 B+ 树取其他字段,I/O 暴增。
- 用
EXPLAIN FORMAT=JSON查看rows_examined_per_scan和filtered字段,确认实际扫描量是否远超预期 - 检查
SELECT *是否导致大量回表——改成只查索引已包含的字段(即「覆盖索引」) - 复合索引顺序是否匹配查询条件和排序需求?
INDEX(a,b,c)对WHERE b=1 ORDER BY c是无效的
如何判断是不是磁盘 I/O 成了瓶颈?
不是所有“慢”都出在 SQL 本身。当索引扫描量合理、执行计划正常,但响应时间波动大、并发一高就雪崩,就要怀疑底层 I/O。
关键看 SHOW PROFILE 或性能模式(performance_schema)中 wait/io/file 类型等待是否占主导;或者用系统命令观察:
-
iostat -x 1看%util是否持续 > 80%,await是否 > 20ms(机械盘)或 > 2ms(SSD) -
pt-ioprofile可直接定位 MySQL 进程正在读哪些文件(如ibdata1、.ibd) - 检查
innodb_buffer_pool_size是否太小——若Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests> 0.01,说明缓存命中率差,频繁刷盘
ORDER BY + 索引不生效,却还在扫全索引?
即使 WHERE 条件命中索引,ORDER BY 字段不在索引里,或顺序不一致,MySQL 就无法利用索引排序,会触发 Using filesort —— 这个阶段可能把上万行数据拉到内存/临时文件排序,I/O 和 CPU 都吃紧。
- 确认
ORDER BY字段是否已包含在联合索引最右位置,且方向一致(如索引是(a,b,c ASC),则ORDER BY a,b,c有效,ORDER BY a,b,c DESC在 MySQL 8.0+ 才支持 - 避免
ORDER BY RAND()或函数表达式(如ORDER BY UPPER(name)),它们必然无法走索引 - 如果只是分页查前 N 条,考虑用「游标分页」替代
LIMIT offset,N,避免每次从头扫
临时表写磁盘导致 I/O 爆增,怎么发现和绕过?
当查询涉及 GROUP BY、DISTINCT、多表 JOIN 且无合适连接索引时,MySQL 可能创建内部临时表。若结果集大,默认用 MyISAM 引擎落地磁盘(tmp_table_size 和 max_heap_table_size 共同限制内存上限)。
- 查
SHOW STATUS LIKE 'Created_tmp%':若Created_tmp_disk_tables增长快,说明频繁落盘 - 用
EXPLAIN看是否有Using temporary;配合FORMAT=JSON查using_temporary_table字段 - 调大
tmp_table_size和max_heap_table_size(需两者一致),但别超过物理内存 20%,否则 OOM 风险上升 - 更治本的是改写 SQL:加索引加速
GROUP BY字段,或拆成子查询减少中间结果集
真正卡住的往往不是那条 SELECT 语句本身,而是它背后触发的随机 I/O、临时文件刷写、或缓冲池争抢——这些细节不进 EXPLAIN,得靠系统指标和执行时的等待事件反推。


















