MySQL 8.4 EXPLAIN:从访问路径到实际执行
慢 SQL 的关键问题是“在哪里读了多少数据,又重复了多少次”。本文限定 MySQL 8.4、InnoDB;普通 EXPLAIN 展示优化器计划,不能证明真实耗时。下列 SQL 和数字均为教学示意,未连接数据库执行;本机 PATH 仅发现 5.7 程序,不能用其结果替代 8.4 验证。
访问类型:看取数方式,不背性能排行榜
显式使用 EXPLAIN FORMAT=TRADITIONAL SELECT ... 查看表格,避免受 explain_format 设置影响;树形结构使用 FORMAT=TREE。
| type | 取数方式 | 阅读时要追问 |
|---|---|---|
| system / const | 单行表特例 / 主键或非空唯一键完整等值定位至多一行 | 是否完整匹配唯一键? |
| eq_ref | 对前序表每个行组合,按主键或非空唯一键找到至多一行 | 外层组合有多少? |
| ref | 非唯一索引或索引左前缀等值匹配,可返回多行 | 每次匹配多少行? |
| range | 索引区间访问 | 区间是否过宽? |
| index | 扫描索引树 | 是否仍需扫完整个索引? |
| ALL | 全表扫描 | 小表或大量命中时是否合理? |
这不是完整枚举,也不是耗时排名。eq_ref 重复百万次可能很贵,几十行小表的 ALL 可能合理。possible_keys 是候选,key 是实际选择;key_len 是使用键长度的线索,不等于命中行数。字段定义见官方 EXPLAIN 输出。
rows 与 filtered:先估算访问,再估算留下
字段名是 filtered,不是 filter。rows 表示优化器估计需检查的行数,InnoDB 下不是精确计数;filtered 是这些行经过表条件后留下的百分比。假设 rows=10000, filtered=2,预计留下 10000 × 2 / 100 = 200 行,并非丢掉 2%。
在嵌套循环连接中,外层若产出 200 行,内层每次估计访问 30 行,就可能有约 6000 次行检查;这不是把所有计划行的 rows 相加。上述是理解成本的简化模型,不能套给哈希连接或当作实际 I/O 次数。参见官方 条件过滤。
Extra:区分覆盖、下推与额外工作
Using index 表示可从索引取得查询所需列,通常称覆盖索引;它不保证扫描行数少。Using where 表示还需条件筛选,也不能直接判定索引失效。
Using index condition 是索引条件下推:先用索引中的字段判定一部分条件,符合后才读取完整记录,目的是减少回表;它与覆盖索引不是一回事。见官方 ICP。
Using filesort 表示额外排序,可能在内存中完成,不等于一定写磁盘;Using temporary 表示使用内部临时表,也不能单凭名字判断落盘。应结合输入量和耗时定位。排序细节见官方 ORDER BY 优化。
索引:让条件、顺序与投影对齐
以下仅在独立测试库准备示例结构,再导入具有代表性分布的数据;空表或几行数据无法证明性能。
CREATE TABLE explain_orders (
id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL,
status TINYINT NOT NULL,
created_at DATETIME NOT NULL,
amount DECIMAL(12,2) NOT NULL,
KEY idx_tsc (tenant_id, status, created_at)
) ENGINE=InnoDB;
EXPLAIN FORMAT=TRADITIONAL
SELECT id, created_at FROM explain_orders
WHERE tenant_id = 42 AND status = 1
AND created_at >= '2026-01-01'
ORDER BY created_at LIMIT 20;该索引先按租户、状态等值缩小范围,再按时间取区间,有机会同时利用索引顺序。InnoDB 二级索引带主键,当前投影有覆盖机会;加上 amount 后需要重新评估回表成本。这里描述可行路径,不承诺一定出现某个 type 或 Extra。
联合索引通常从最左前缀利用;仅按 status 查询不能期待同等定位能力。范围列之后的列也并非“全部失效”,仍可能参与过滤或覆盖。索引越多,写入和空间维护成本越高。见官方 多列索引。
ANALYZE:从估算切换到真实执行
假设某节点显示 actual time=0.04..0.30 rows=8 loops=100:时间单位为毫秒,分别表示首行与完成时间;多循环时按循环平均,行数也应结合循环理解,总产出约 800 行。节点耗时包含子节点,不能把整棵树耗时相加。以上数字是解释用的假设值。
优先寻找估算与实际行数偏差明显、循环多的节点,再向下追取数源头。实际执行会消耗 CPU、I/O,可能产生排序、临时表与锁等待;一次热缓存结果也不代表线上并发延迟。8.4 还支持对多表 UPDATE、DELETE 做 ANALYZE,它们会执行修改,不能当作只读诊断。应先看普通计划,再在隔离数据上执行,并准备 KILL QUERY 中止手段。
预期失败样例:EXPLAIN ANALYZE FORMAT=JSON SELECT 1; 在 8.4 不支持;ANALYZE 只支持 TREE。此处未运行,不提供伪造错误输出。上述格式、执行范围及计时规则见官方 EXPLAIN 语句。
统计信息:修正估算,不制造索引
大量导入或数据倾斜后,先比较代表性参数的估算与实际,再考虑更新统计信息:
ANALYZE TABLE explain_orders;
ANALYZE TABLE explain_orders
UPDATE HISTOGRAM ON status WITH 32 BUCKETS;前者更新键分布统计,后者提供列值分布信息;直方图不创建访问路径,也不能保证优化器选用它。采样有误差,单列分布也无法完整表达租户与状态的相关性。ANALYZE TABLE 本身有开销,分析 InnoDB 表时会持有读锁,应安排合适窗口,不能混同于 EXPLAIN ANALYZE。见官方 ANALYZE TABLE。
延伸阅读:MySQL 知识梳理、索引结构资料、事务隔离级别资料、MVCC 与可见性。这些保留站内阅读路径,技术依据以上述官方手册为准。
