计划里的表访问顺序
多表连接的经典计划输出是一张文字表格,每个表占一行,行的先后顺序就是优化器决定的访问顺序:排在前面的先被读取并产出连接键,排在后面的按索引逐行查找。判断顺序不能看 SQL 里 FROM 的书写次序,优化器会按代价重排。另外要注意编号列:编号相同的行属于同一个查询块,编号不同则分属不同的子查询或集合运算分支,跨编号谈顺序没有意义。
基本信息
| 适用平台 | MySQL 8.0 / PostgreSQL 12 及以上 |
|---|---|
| 顺序判定 | 同一查询块内,行序即访问顺序,自上而下 |
| 编号含义 | 查询块标识,子查询与集合运算会引入新编号 |
| 关注字段 | 访问类型、用到的索引、估算行数、额外信息 |
| 新版变化 | MySQL 8.0.18 起另有树形格式的分析输出,信息更细 |
典型使用场景
- 确认哪张表被当作驱动表,是否符合数据分布预期
- 排查连接慢:看被驱动表的访问方式是否退化成全表扫描
- 验证索引是否被使用,以及用的是不是预期的那个索引
- 对比改写前后的计划差异,评估改写是否真的有效
- 归档慢查询证据,供后续复盘与容量评估使用
读计划的顺序
读计划的顺序
EXPLAIN
SELECT u.name, o.total
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.city = 'shanghai' AND o.status = 'paid';
-- 输出中 users 行在上、orders 行在下:
-- 先访问 users(驱动侧),再按连接键查 orders
-- 反向顺序也常出现:优化器认为 orders 过滤后更小就会调换
-- 需要更细的执行信息时用分析模式(会真实执行语句)
EXPLAIN ANALYZE
SELECT u.name, o.total
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.city = 'shanghai' AND o.status = 'paid';
-- 干预顺序的手段(MySQL 可指定强制连接顺序,PostgreSQL 可调整连接重排上限)关键列读法
| 列 | 含义 | 异常信号 |
|---|---|---|
| 编号列 | 查询块编号 | 跨编号比较顺序得出的结论通常是错的 |
| 表名列 | 本次访问的对象 | 出现衍生表或临时表说明存在物化步骤 |
| 访问类型 | 单行、索引、范围、全表 | 出现全表扫描且行数大时要重点看 |
| 索引列 | 实际选用的索引 | 显示未用索引说明条件无法利用现有索引 |
| 估算行数 | 预计扫描行数 | 与真实行数差一两个数量级说明统计信息失真 |
| 额外信息 | 排序、临时表、下推等提示 | 出现临时表与排序说明中间结果较大 |
驱动表的选择不是越小的表越好,而是「过滤之后产出行数最少」的一侧更适合当驱动。判断依据是过滤后的估算行数,而不是表的原始大小。
分析模式会真实执行语句,涉及写操作时务必先改写成等价的查询,或在事务里执行后回滚,不要在生产环境直接跑。