人人都会AI编程

附录 B EXPLAIN 字段与索引优化速查表

更新时间:2026-07-11

本速查表专注于日常开发中最常用的 EXPLAIN 输出字段,以及索引优化中最高频的判断标准。建议在排查慢 SQL 时对照使用。

B.1 EXPLAIN 核心字段速查

| 字段 | 含义 | 关键关注点 |
|------|------|------------|
| id | 查询执行顺序编号 | 数字越大越先执行;相同数字从上到下执行;通常表示多表关联时的执行次序 |
| select_type | 查询类型 | SIMPLE:简单查询,不用子查询或 UNION;<br>PRIMARY:最外层查询;<br>SUBQUERY:SELECT 或 WHERE 中的子查询;<br>DERIVED:FROM 中的子查询(派生表);<br>UNION:UNION 中的后续查询;<br>DEPENDENT SUBQUERY:依赖外层查询的子查询,通常性能较差 |
| table | 访问的表名或别名 | 若显示 <derivedN><unionM,N>,表示访问的是临时表派生表 |
| type | 最重要的性能指标<br>表示访问数据的方式 | 从好到坏排序:<br>system / const:主键或唯一键等值访问,最多一条记录,最优<br>eq_ref:连接时用主键或唯一键匹配,驱动表的每一行在该表只匹配一行<br>ref:通过非唯一索引或联合索引的最左前缀等值匹配,可能匹配多行<br>range:索引范围扫描,如 BETWEEN>INOR 等<br>index:全索引扫描,遍历整个索引树,通常比全表扫描快但不理想<br>ALL:全表扫描,是红色警告,必须重点优化 |
| possible_keys | MySQL 认为可能使用的索引 | 列出查询中可用到的索引;若为 NULL 表示没有可用索引 |
| key | 实际使用的索引 | 若为 NULL 表示未使用任何索引;可与 possible_keys 对比,若列了索引却未用,说明优化器认为使用索引代价更高,通常是统计信息不准或索引失效 |
| key_len | 实际使用的索引字节数 | 可用于判断使用了联合索引的哪几列,数值越短表示只用到了前缀部分 |
| ref | 索引列上被关联的字段或常量 | 显示与索引列进行比较的值来源,如 const(常量)、数据库.表.列(关联字段) |
| rows | 预估扫描行数 | 优化器估算需要检查的行数,值越小越好,但只是估算,不一定准确 |
| filtered | 按表条件过滤后剩余记录占比 | 百分比越高,表示过滤后返回的行越多;与 rows 结合可估算最终结果集大小 |
| Extra | 额外的执行信息 | ⚠️ 重点关注的常见值见下方特别说明 |

B.2 Extra 字段关键信息速查

| Extra 值 | 含义与处理建议 |
|-----------|----------------|
| Using index | 覆盖索引:查询列全部可以从索引中获取,无需回表,性能好 |
| Using where | 在存储引擎层返回行后,由服务层再用 WHERE 条件过滤;若没有 Using index,可能行较多 |
| Using index condition | 索引下推(ICP):存储引擎层使用索引过滤部分 WHERE 条件,减少回表次数,属于优化 |
| Using temporary | 内部临时表:通常出现在 GROUP BY、DISTINCT、ORDER BY 等操作,如果结果集较大,需关注是否有合适索引避免临时表 |
| Using filesort | 文件排序:无法利用索引完成排序,需要在内存或磁盘中排序,数据量大时影响性能,建议尝试为排序字段加索引 |
| Using join buffer | 连接时使用了连接缓冲区,通常因为被驱动表没有合适的索引,应检查是否缺少索引导致全表扫描连接 |
| Impossible WHERE | WHERE 条件永远为假,查询可能不返回任何行 |
| Select tables optimized away | 优化器通过索引直接返回结果,无需访问表,比如 COUNT(*) 无 WHERE 时从统计信息直接获取 |
| No tables used | 查询中没有涉及表,如 SELECT 1 |

B.3 索引优化速查

B.3.1 索引设计原则

  • 频繁作为查询条件、排序、分组的列优先建索引
  • 区分度高的列在前:联合索引中,唯一值多的列放前面,符合最左前缀原则
  • 尽量创建联合索引以覆盖查询,减少回表
  • 避免过多索引:索引会拖慢写入速度,且占用空间,一般单表控制在 5 个以内
  • 长字符串用前缀索引KEY idx_name (name(10)) 仅索引前若干字符

B.3.2 常见索引失效场景(务必记住)

| 失效场景 | 案例 | 原因 |
|----------|------|------|
| 对索引列使用函数或运算 | WHERE DATE(create_time) = '2025-01-01' | 优化器无法使用索引,应用 WHERE create_time >= '2025-01-01' AND create_time < '2025-01-02' |
| 隐式类型转换 | WHERE phone = 13900000000(phone 是 VARCHAR) | MySQL 会将字符串列转为数值,导致索引失效,应写 '13900000000' |
| 前导模糊查询 | WHERE name LIKE '%keyword' | 查询以通配符开头时,B+ 树无法利用有序性,索引失效;'keyword%' 是允许的 |
| 联合索引不满足最左前缀 | 联合索引 (a, b, c)<br>WHERE b=1 AND c=2 | 跳过了第一列 a,索引失效;必须从最左列连续匹配 |
| 范围查询右侧列失效 | 联合索引 (a, b, c)<br>WHERE a=1 AND b>10 AND c=3 | 索引在 b 列做了范围扫描后,c 列无法继续使用索引 |
| OR 连接非索引列 | WHERE indexed_col = 1 OR non_indexed_col = 2 | OR 两边只要有一边不是索引,整体都可能变成全表扫描;可考虑改用 UNION ALL |
| 数据量太小 | 表只有几百行 | 优化器认为全表扫描更快,索引可能不启用,不必纠结 |
| 使用负向条件 | WHERE status != 1NOT IN | 一般不能有效使用索引,可改写为范围查询或使用覆盖索引 |

B.3.3 索引优化技巧速记

  • 覆盖索引SELECT 的列全部包含在索引中 → Extra: Using index,无需回表
  • 索引下推 (ICP):索引中有部分条件可以在引擎层过滤 → Extra: Using index condition
  • 前缀索引:对于较长字符串,只索引前 N 位,节省空间,但可能降低区分度
  • 冗余索引清理:如已有联合索引 (a, b),则单独的 (a) 索引就是冗余的,应删除
  • 使用 FORCE INDEX 谨慎:仅在优化器错误选索引时临时使用,优先通过更新统计信息(ANALYZE TABLE)或重写 SQL 解决
  • 定期OPTIMIZE TABLE 或重建索引:对于大量增删改的表,可以整理碎片并更新统计信息

B.4 快速排障流程

  1. 拿到慢 SQL → 执行 EXPLAIN SELECT ...
  2. type 列:如果是 ALLindex,立刻检查是否缺失索引或索引失效
  3. key 列:确认实际使用的索引是否符合预期,对照 possible_keys
  4. Extra:出现 Using filesortUsing temporary 时考虑为排序/分组列建索引
  5. rows:联合 filtered 估算实际扫描行数,若预估行数远大于实际需要,优化器统计信息可能有误,可执行 ANALYZE TABLE 更新
  6. 结合 B.3.2 检查 SQL 中是否存在索引失效写法,修改后重新验证

以上速查覆盖了 90% 日常开发中的索引与执行计划问题,当你遇到复杂情况时,再回到第 11 章和第 12 章深入分析即可。