bigywater.tech
MySQL / InnoDB · 12

覆盖索引:为什么查询可以不回表

用EXPLAIN ANALYZE对比约2万行范围查询在覆盖索引和非覆盖索引下的执行耗时,看回表这一步要多付出多少代价。

5~6倍
覆盖索引 vs 回表查询的耗时差距
2ms vs 12ms
同一条件下两种查询的耗时区间
约2万行
本文范围查询涉及的行数

做的事:同一个范围条件,查覆盖的列 vs 查不覆盖的列

还是上一篇那张10万行表 t1(idx_k索引在k列上),这次测的是查询耗时,不是理论cost。

同一个WHERE条件,只改SELECT的列SELECT k FROM t1 WHERE k BETWEEN 0 AND 199 -- 只查索引列本身,约2万行 SELECT v FROM t1 WHERE k BETWEEN 0 AND 199 -- 查非索引列v,同样约2万行

"覆盖索引"指的是:一条查询要的所有列,二级索引里全都有,不需要再去聚簇索引里"回表"取数据。这次直接用 EXPLAIN ANALYZE 测执行时间,不只是看理论cost。

验证:2万行范围查询,耗时差了5-6倍

同一条件,重复跑3次,取的EXPLAIN ANALYZE耗时。

耗时(3次重复,单位ms)SELECT k FROM t1 WHERE k BETWEEN 0 AND 199 (覆盖索引,Covering index range scan) → 2.64 / 1.90 / 2.65 / 1.91 / 2.68 / 1.93 SELECT v FROM t1 WHERE k BETWEEN 0 AND 199 (非覆盖,需要回表,Index range scan) → 13.5 / 11.1 / 12.8 / 10.4 / 13.0 / 10.6

数字很稳定:覆盖索引查询稳定在 1.9~2.7 毫秒,非覆盖查询稳定在 10.4~13.5 毫秒——每次重复测都差了 5到6倍。查询逻辑完全一样,唯一区别是SELECT的列在不在索引里,这个差距完全来自"要不要回表"这一件事。

差距的来源很直接:覆盖索引只需要遍历 idx_k 这一棵树,约2万条索引记录,顺序读,磁盘/内存访问模式很规整;非覆盖查询在遍历 idx_k 找到约2万个匹配的主键值后,还要针对每一个匹配的主键值,单独去聚簇索引里做一次树查找——这2万次额外的树查找,才是耗时暴涨的原因。

为什么覆盖索引在实践中经常被当作优化手段

同一份数据,只是SELECT的列变了,性能就能差出5倍。

这也是为什么"只SELECT你真正需要的列"、"把常用的查询列也加进索引里"是有效的优化手段——不是玄学,是因为它能让查询完全命中覆盖索引,跳过昂贵的回表这一步。反过来,如果一张大表的高频查询总是需要"索引之外的列",那不管索引设计得多合理,回表这一步的代价始终省不掉,除非把那些列也纳入索引(当然索引本身也会变大,需要权衡)。

分步演示:覆盖索引省下的那5倍时间

每一步都是本文上面用EXPLAIN ANALYZE跑出来的原始耗时。

同一条件,覆盖索引 vs 回表,差5倍以上
这一步新验证的重复次数

    本文用到的工具

    • 本机 MySQL 26.7.0(InnoDB),10万行表,约2万行范围查询
    • EXPLAIN ANALYZE:执行并返回实际耗时(actual time)
    京ICP备2026067502号-1京公网安备11010502063487号