← 中文资源

简体中文版本

在 MySQL 中把全表扫描变成覆盖查询

用 EXPLAIN 确认全表扫描,设计复合覆盖索引,验证 Extra 出现 Using index 后查询不再回表。

阅读英文原文

目标

排行榜只需要带三个字段的一百行,默认访问路径却在每次请求时读取两百万条完整行并排序。EXPLAIN 把这一切直白地写成 type=ALL 与 Using filesort。一个“过滤列打头、排序列其次、再带上被选列”的复合索引,能把同一查询变成按序读一百条索引项、全程不回表。计划输出让任何人在相信之前先看到证据。

步骤要点

  1. 先 EXPLAIN 确认基线:type=ALL、rows=两百万、Extra 含 Using filesort,坐实全表扫描加排序。
  2. 设计覆盖索引 (season, score DESC, user_id, updated_at):等值列在前、排序列其次、SELECT 列殿后。
  3. 重建计划验证 type 变 ref/range、rows=100、Extra 出现 Using index,即索引覆盖、免回表免排序。
  4. 用 profiling 或 digest 统计实测耗时从数百毫秒 CPU 降到亚毫秒索引读。
  5. 把 EXPLAIN FORMAT=JSON 的计划断言接入 CI,防止后续改动悄悄退回全表扫描。

排错提示

Extra 仍显示 Using filesort,多半是排序列方向或索引列顺序与查询不匹配,应按“等值过滤优先、排序列随后”重排;索引存在却不被选用时用 FORCE INDEX 验证可用性并检查统计信息新鲜度;写吞吐下滑说明索引带来写放大,应删掉被新复合索引覆盖的冗余单列索引并监控变更速率。

适用边界

覆盖索引用存储与写成本换读速度,对极少运行的查询不划算;索引内 DESC 仅 MySQL 8.0 起生效,旧版本会忽略并照旧 filesort。

代码示例

EXPLAIN SELECT user_id, score, updated_at
FROM leaderboard
WHERE season = 2026
ORDER BY score DESC LIMIT 100;

-- Before:  type=ALL  rows=2,000,000  Extra: Using where; Using filesort
-- After:   type=ref  rows=100        Extra: Using index

CREATE INDEX idx_season_score_cover
ON leaderboard (season, score DESC, user_id, updated_at);