3、SQL执行计划基础:explain命令解读、type列详解与rows/filtered分析
3.1 为什么执行计划是性能调优的“第一现场”
在批次追溯数据场景中,采购计划优先级排序往往涉及多表关联(批次表、采购订单表、供应商表、质检表)和大量范围查询(日期、批次号、优先级分数)。SQL执行计划是数据库优化器为一条SQL语句生成的“操作路线图”,它直接决定了查询是秒级返回还是分钟级等待。
通过 EXPLAIN 命令,我们可以透视优化器的选择:是否使用了索引、扫描了多少行、关联顺序如何。对于批次追溯这类数据量大、过滤条件复杂的查询,读懂执行计划是性能调优的起点。
EXPLAIN 验证其执行计划。如果 type 列出现 ALL(全表扫描),且 rows 超过万级,该查询几乎必然成为性能瓶颈。
3.2 EXPLAIN 命令解读:输出列与核心含义
在 MySQL 中,使用 EXPLAIN SELECT ... 即可查看执行计划。输出结果包含多个关键列,下表列出了与批次追溯性能最相关的列及其含义:
| 列名 | 含义 | 批次追溯场景关注点 |
|---|---|---|
| id | 查询中每个SELECT子句的标识符,id越大越先执行 | 多表关联时,观察子查询或派生表的执行顺序 |
| select_type | 查询类型(SIMPLE、PRIMARY、SUBQUERY、DERIVED等) | 避免DEPENDENT SUBQUERY,它会导致逐行执行子查询 |
| table | 当前操作的表名或别名 | 确认是否访问了正确的批次表、订单表 |
| type | 访问方法(性能从好到差:system > const > eq_ref > ref > range > index > ALL) | 核心关注列,必须达到range及以上 |
| possible_keys | 可能使用的索引列表 | 检查批次号、日期字段的索引是否被识别 |
| key | 实际使用的索引名称 | 确认优化器是否选择了正确的复合索引 |
| key_len | 使用的索引字节长度 | 判断复合索引是否被充分使用(长度越长,用到的索引列越多) |
| ref | 与索引比较的列或常量 | 关联查询时,确认关联字段是否使用了索引 |
| rows | 优化器估算的需要扫描的行数 | 性能估算核心,值越小越好 |
| filtered | 经过表条件过滤后,剩余行数占总行数的百分比 | 值越低,说明索引过滤性越好;值高(如100%)可能意味着缺少索引 |
| Extra | 额外信息(Using index、Using where、Using filesort等) | 出现 Using filesort 或 Using temporary 时需警惕 |
type 定好坏,再看 rows 估代价,最后 Extra 查隐患。
3.3 type 列详解:从全表扫描到常量查找
type 列是执行计划中最重要的性能指标。它描述了MySQL如何访问表中的数据。下面按性能从最差到最优的顺序逐一解读:
3.3.1 ALL —— 全表扫描(性能灾难)
MySQL 将读取表中的所有数据行,然后通过 WHERE 条件过滤。在批次表数据量达到百万级时,一次 ALL 扫描可能耗时数秒甚至数十秒。
- 触发场景: 查询条件未使用索引,或优化器认为全表扫描比使用索引更快(通常发生在小表或查询返回大部分数据时)。
- 批次追溯案例:
SELECT * FROM batch WHERE status = 'ACTIVE',如果status字段没有索引,将触发全表扫描。 - 调优目标: 必须消除
ALL,至少提升到index或range。
3.3.2 index —— 全索引扫描
MySQL 扫描整个索引树来获取数据。虽然比全表扫描快(因为索引通常比数据小),但仍然需要遍历所有索引条目。
- 触发场景: 查询需要覆盖索引中的所有行,或者查询的列都在索引中但无法利用索引的过滤性。
- 批次追溯案例:
SELECT batch_id, batch_date FROM batch ORDER BY batch_date,如果batch_date有索引,但需要返回所有行,则可能使用index扫描。 - 调优建议: 如果查询只需要部分数据,应添加
WHERE条件将其转为range。
3.3.3 range —— 索引范围扫描(批次追溯的及格线)
MySQL 只扫描索引中满足指定范围的行。这是批次追溯查询的最低可接受标准。
- 触发场景: 使用
BETWEEN、>、<、IN()、LIKE 'prefix%'等范围操作符,且对应字段有索引。 - 批次追溯案例:
SELECT * FROM batch WHERE batch_date BETWEEN '2024-01-01' AND '2024-01-31',如果batch_date有索引,则使用range。 - 调优要点: 确保范围查询的字段在复合索引中位于最左侧,或单独建立索引。
3.3.4 ref —— 非唯一索引等值匹配
MySQL 使用非唯一索引或唯一索引的前缀进行等值匹配,返回可能匹配的多行。
- 触发场景: 使用
=操作符关联或过滤,且关联字段是非唯一索引。 - 批次追溯案例:
SELECT * FROM batch WHERE supplier_id = 100,如果supplier_id有非唯一索引。 - 性能特点: 比
range更快,因为等值匹配的扫描范围更精确。
3.3.5 eq_ref —— 唯一索引等值匹配(最优关联)
MySQL 使用唯一索引或主键进行等值匹配,对于每个驱动表的行,最多返回一行匹配数据。这是多表关联查询中最理想的关联类型。
- 触发场景: 多表
JOIN时,被驱动表的关联字段是主键或唯一索引。 - 批次追溯案例:
SELECT * FROM purchase_order po JOIN batch b ON po.batch_id = b.id,其中b.id是主键。 - 调优目标: 所有基于主键或唯一键的关联查询,都应达到
eq_ref。
3.3.6 const —— 常量查找(性能巅峰)
MySQL 将查询条件优化为常量,最多返回一行数据,且读取速度极快(通常只需读取一次索引)。
- 触发场景: 使用主键或唯一索引的等值查询,且条件值是常量。
- 批次追溯案例:
SELECT * FROM batch WHERE id = 12345。 - 性能特点: 几乎零成本,是性能调优的终极目标。
3.4 type 列性能对比速查表
| type 值 | 性能等级 | 扫描方式 | 典型场景 | 批次追溯可接受度 |
|---|---|---|---|---|
| ALL | ★☆☆☆☆ | 全表扫描 | 无索引过滤 | 不可接受,必须优化 |
| index | ★★☆☆☆ | 全索引扫描 | 覆盖索引但无过滤 | 仅小表可接受 |
| range | ★★★☆☆ | 索引范围扫描 | 日期范围、IN列表 | 最低及格线 |
| ref | ★★★★☆ | 非唯一索引等值 | 供应商ID、状态码 | 良好 |
| eq_ref | ★★★★★ | 唯一索引等值 | 主键关联 | 最优关联 |
| const | ★★★★★ | 常量查找 | 主键单行查询 | 最优单行 |
3.5 rows 与 filtered 分析:估算查询代价
rows 和 filtered 两列共同决定了查询的实际扫描代价。
3.5.1 rows —— 优化器估算的扫描行数
rows 是优化器根据索引统计信息估算的、需要读取的行数。这个值越接近实际返回的行数,说明索引的过滤性越好。
- 解读方法: 如果
rows为 100000,但实际查询只返回 10 行,说明索引过滤性差,需要优化索引设计。 - 批次追溯案例: 查询
SELECT * FROM batch WHERE status = 'PENDING' AND priority_score > 80,如果rows显示 50000,但实际只有 500 行满足条件,说明status索引的区分度太低。 - 调优动作: 考虑建立
(status, priority_score)复合索引,让priority_score的范围过滤提前生效。
3.5.2 filtered —— 过滤后的剩余百分比
filtered 表示在 rows 估算的行数中,经过 WHERE 条件过滤后,实际需要返回的行数占比。值越低,说明索引过滤效果越好。
- 计算公式: 实际扫描行数 ≈
rows×filtered/ 100。 - 解读方法:
filtered = 100%通常意味着索引没有起到过滤作用,所有扫描的行都返回了;filtered = 10%意味着索引过滤掉了 90% 的行。 - 批次追溯案例: 执行计划显示
rows = 10000,filtered = 5%,则实际扫描行数约为 500 行,性能良好。
rows 而忽略 filtered。一个 rows = 1000 但 filtered = 100% 的查询,实际扫描了 1000 行;而一个 rows = 5000 但 filtered = 10% 的查询,实际只扫描了 500 行。后者性能反而更好。
3.6 综合案例:批次追溯查询的执行计划分析
假设我们有一个批次追溯查询,用于计算采购计划优先级排序:
EXPLAIN
SELECT b.batch_id, b.batch_date, b.priority_score, po.order_amount
FROM batch b
JOIN purchase_order po ON b.batch_id = po.batch_id
WHERE b.batch_date BETWEEN '2024-06-01' AND '2024-06-30'
AND b.status = 'ACTIVE'
AND po.priority_level > 3
ORDER BY b.priority_score DESC;
假设执行计划输出如下:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | b | range | idx_batch_date, idx_status | idx_batch_date | 3 | NULL | 12000 | 10% | Using where; Using filesort |
| 1 | SIMPLE | po | ref | idx_batch_id, idx_priority | idx_batch_id | 4 | batch.b.batch_id | 1 | 50% | Using where |
分析解读:
- 表 b(批次表):
type = range,使用了idx_batch_date索引进行日期范围扫描,达到及格线。但rows = 12000,filtered = 10%,实际扫描约 1200 行,说明日期索引过滤性一般(一个月内仍有大量批次)。Extra显示Using filesort,说明排序未使用索引,需要额外排序操作。 - 表 po(采购订单表):
type = ref,使用idx_batch_id索引进行等值关联,性能良好。rows = 1,说明每个批次号在订单表中通常只有一条记录。 - 优化建议: 为批次表建立复合索引
(batch_date, status, priority_score),让status过滤和priority_score排序都能利用索引,消除Using filesort。
3.7 执行计划分析流程总结
在批次追溯性能调优中,建议按以下步骤分析执行计划:
- 第一步:检查 type 列 —— 确保所有表的访问类型至少为
range。如果出现ALL,立即添加索引。 - 第二步:检查 key 列 —— 确认实际使用的索引是否与查询条件匹配。如果
key为 NULL,说明没有使用索引。 - 第三步:计算实际扫描行数 —— 用
rows×filtered/ 100 估算真实扫描量。如果超过万级,考虑优化索引或改写查询。 - 第四步:检查 Extra 列 —— 消除
Using filesort(通过排序字段建索引)和Using temporary(通过优化GROUP BY或DISTINCT)。 - 第五步:验证优化效果 —— 修改后再次执行
EXPLAIN,对比rows和type的变化。
3.8 本章小结
- EXPLAIN 是性能调优的起点:任何批次追溯查询上线前,必须用 EXPLAIN 验证执行计划。
- type 列决定访问效率:从 ALL(全表扫描)到 const(常量查找),性能逐级提升。批次追溯查询至少应达到
range级别。 - rows 和 filtered 联合估算代价:实际扫描行数 = rows × filtered / 100,这个值才是真正的性能瓶颈指标。
- Extra 列暴露隐藏问题:
Using filesort和Using temporary是性能杀手,应通过索引优化消除。 - 分析流程可重复:每次索引调整后,重新执行 EXPLAIN 对比变化,形成“分析-优化-验证”的闭环。