目标
普通 EXPLAIN 只展示优化器打算做什么,其行数估计常常错一个数量级;EXPLAIN ANALYZE 会真正执行查询并汇报每一步实际发生了什么,是证明“索引确实有效”的唯一诚实手段。本食谱用前后对比的方式度量真实行数与耗时,让加索引这件事从占卜变成实验,并把写入代价也算进账本。
步骤要点
- 先用 EXPLAIN ANALYZE 抓基线计划,读出每步的 actual time 与 actual rows,确认 orders 表全表扫描读了两百多万行。
- 用 EXPLAIN FORMAT=JSON 对比估计行数与真实行数的差距,差距大说明统计信息有偏差,优化器在误判。
- 按谓词形态创建复合索引:等值列 status 在前、范围列 created_at 在后,然后 ANALYZE TABLE 刷新统计。
- 重跑 EXPLAIN ANALYZE 逐步对比:索引范围扫描只读四万行,总耗时从 7900ms 掉到 45ms。
- 查 innodb_index_stats 里的索引体积,把读取收益与每次写入的放大代价放在同一张账上看,再决定索引去留。
排错提示
优化器不理新索引多半是复合索引列序不匹配谓词,等值列放最前、范围列放最后,再刷新统计;EXPLAIN ANALYZE 会真实执行一次,病态查询有真实代价,先在只读副本或限行数环境下诊断;预发有收益生产没有通常是数据分布不同,低峰期在生产跑一次 ANALYZE TABLE 让统计贴近现实再复查。
适用边界
EXPLAIN ANALYZE 只执行一次,计时包含一次热缓存运行,不能当基准测试用;复合索引列序是为特定查询服务的,帮到这条查询的索引对别条可能是死重,所以要看体积账。
代码示例
-- Before: full scan, 2.4M rows
EXPLAIN ANALYZE SELECT o.id FROM orders o
WHERE o.created_at >= '2026-10-01' AND o.status = 'shipped';
-- -> Table scan on o (actual rows=2.4M, time=7900 ms)
ALTER TABLE orders ADD INDEX idx_status_created (status, created_at);
ANALYZE TABLE orders;
-- After: index range, 40k rows
EXPLAIN ANALYZE SELECT o.id FROM orders o
WHERE o.created_at >= '2026-10-01' AND o.status = 'shipped';
-- -> Index range scan on idx_status_created (actual rows=40k, time=45 ms)