目标
排行榜只需要带三个字段的一百行,默认访问路径却在每次请求时读取两百万条完整行并排序。EXPLAIN 把这一切直白地写成 type=ALL 与 Using filesort。一个“过滤列打头、排序列其次、再带上被选列”的复合索引,能把同一查询变成按序读一百条索引项、全程不回表。计划输出让任何人在相信之前先看到证据。
步骤要点
- 先 EXPLAIN 确认基线:type=ALL、rows=两百万、Extra 含 Using filesort,坐实全表扫描加排序。
- 设计覆盖索引 (season, score DESC, user_id, updated_at):等值列在前、排序列其次、SELECT 列殿后。
- 重建计划验证 type 变 ref/range、rows=100、Extra 出现 Using index,即索引覆盖、免回表免排序。
- 用 profiling 或 digest 统计实测耗时从数百毫秒 CPU 降到亚毫秒索引读。
- 把 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);