用EXPLAIN ANALYZE对比约2万行范围查询在覆盖索引和非覆盖索引下的执行耗时,看回表这一步要多付出多少代价。
还是上一篇那张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。
同一条件,重复跑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的列在不在索引里,这个差距完全来自"要不要回表"这一件事。
同一份数据,只是SELECT的列变了,性能就能差出5倍。
这也是为什么"只SELECT你真正需要的列"、"把常用的查询列也加进索引里"是有效的优化手段——不是玄学,是因为它能让查询完全命中覆盖索引,跳过昂贵的回表这一步。反过来,如果一张大表的高频查询总是需要"索引之外的列",那不管索引设计得多合理,回表这一步的代价始终省不掉,除非把那些列也纳入索引(当然索引本身也会变大,需要权衡)。
每一步都是本文上面用EXPLAIN ANALYZE跑出来的原始耗时。
EXPLAIN ANALYZE:执行并返回实际耗时(actual time)