Skip to content

MySQL EXPLAIN与执行计划分析

EXPLAIN命令基础

语法

sql
-- 基本语法
EXPLAIN SELECT ...;

-- 也可以用于其他语句
EXPLAIN INSERT ...;
EXPLAIN UPDATE ...;
EXPLAIN DELETE ...;

-- 显示格式化的执行计划
EXPLAIN FORMAT=JSON SELECT ...;
EXPLAIN FORMAT=TREE SELECT ...;  -- MySQL 8.0+

输出格式

  • 传统格式:表格形式展示
  • JSON格式:详细的JSON结构
  • TREE格式:树状结构,更直观

使用场景

  • 分析SQL语句的执行计划
  • 识别查询瓶颈
  • 验证索引使用情况
  • 优化查询语句

执行计划列详解

id

  • 查询的标识符
  • 数字越大,执行优先级越高
  • 相同id,执行顺序从上到下
  • 不同id,执行顺序从大到小

select_type

  • SIMPLE:简单查询,无子查询或UNION
  • PRIMARY:主查询
  • SUBQUERY:子查询
  • DERIVED:派生表
  • UNION:UNION中的第二个或后续查询
  • UNION RESULT:UNION的结果

table

  • 查询的表名
  • <derivedN>:派生表
  • <unionM,N>:UNION结果

partitions

  • 匹配的分区
  • 对于非分区表,显示NULL

type

  • 访问类型,从优到劣:
    • system:表只有一行
    • const:使用主键或唯一索引等值查询
    • eq_ref:连接查询中,被连接表使用主键或唯一索引
    • ref:使用非唯一索引或唯一索引前缀
    • fulltext:使用全文索引
    • ref_or_null:类似于ref,但是包含NULL值
    • index_merge:使用索引合并优化
    • unique_subquery:子查询中使用唯一索引
    • index_subquery:子查询中使用非唯一索引
    • range:范围查询
    • index:全索引扫描
    • ALL:全表扫描

possible_keys

  • 可能使用的索引
  • 多个索引用逗号分隔
  • 显示NULL表示没有可能的索引

key

  • 实际使用的索引
  • 显示NULL表示没有使用索引
  • 使用覆盖索引时,会显示相应的索引

key_len

  • 使用的索引长度
  • 越短越好
  • 计算规则:
    • 字符串:CHAR(n) = n, VARCHAR(n) = n + 1(或2)
    • 数值:TINYINT = 1, SMALLINT = 2, INT = 4, BIGINT = 8
    • 日期:DATE = 3, DATETIME = 8, TIMESTAMP = 4
    • NULL:加1字节

ref

  • 与索引比较的列或常量
  • 显示NULL表示没有使用ref

rows

  • 估计扫描的行数
  • 不是精确值
  • 越小越好

filtered

  • 过滤后的行数百分比
  • 越大越好
  • 表示符合条件的行数占估计行数的比例

Extra

  • 额外信息
  • 重要的值:
    • Using index:使用覆盖索引
    • Using where:使用WHERE条件过滤
    • Using temporary:使用临时表
    • Using filesort:使用文件排序
    • Using join buffer:使用连接缓冲区
    • Using index condition:使用索引条件推送
    • Using MRR:使用多范围读取
    • Using index for group-by:使用索引进行分组

执行计划分析技巧

识别全表扫描

  • type 为 ALL
  • key 为 NULL
  • rows 值较大

识别索引使用问题

  • possible_keys 有值但 key 为 NULL
  • key_len 较短,未使用复合索引的全部列
  • type 为 index,表示全索引扫描

识别排序问题

  • Extra 中包含 Using filesort
  • 尝试添加适当的索引避免文件排序

识别临时表问题

  • Extra 中包含 Using temporary
  • 通常与GROUP BY、DISTINCT、ORDER BY有关
  • 尝试优化查询或添加适当的索引

识别连接问题

  • type 为 ALL 或 index 在连接的表上
  • rows 值较大
  • 尝试为连接列添加索引

索引使用分析

覆盖索引

  • Extra 中包含 Using index
  • 优点:
    • 减少I/O操作
    • 避免回表查询
    • 提高查询性能

索引条件推送

  • Extra 中包含 Using index condition
  • MySQL 5.6+ 特性
  • 优点:减少服务器与存储引擎之间的数据传输

索引合并

  • type 为 index_merge
  • 多个索引的合并使用
  • 适用于多个OR条件的查询

多范围读取

  • Extra 中包含 Using MRR
  • 提高范围查询的性能
  • 减少随机I/O

执行计划案例分析

案例1:简单查询

sql
EXPLAIN SELECT * FROM users WHERE id = 1;

预期结果

  • type: const
  • key: PRIMARY
  • rows: 1

案例2:范围查询

sql
EXPLAIN SELECT * FROM users WHERE age BETWEEN 18 AND 30;

预期结果

  • type: range
  • key: idx_age (如果有年龄索引)
  • rows: 估计的行数

案例3:连接查询

sql
EXPLAIN SELECT * FROM orders JOIN users ON orders.user_id = users.id WHERE users.name = 'John';

预期结果

  • users表:type为ref,使用name索引
  • orders表:type为ref或eq_ref,使用user_id索引

案例4:复杂查询

sql
EXPLAIN SELECT 
    u.name, 
    COUNT(o.id) as order_count 
FROM 
    users u 
LEFT JOIN 
    orders o ON u.id = o.user_id 
WHERE 
    u.age > 20 
GROUP BY 
    u.id 
ORDER BY 
    order_count DESC 
LIMIT 10;

分析

  • 检查是否使用了适当的索引
  • 注意是否有Using temporary或Using filesort
  • 估计的行数是否合理

JSON格式执行计划

优势

  • 提供更详细的信息
  • 包含成本估算
  • 支持更复杂的分析

示例

sql
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE id = 1;

关键字段

  • query_block:查询块信息
  • cost_info:成本信息
  • table:表信息
  • access_type:访问类型
  • possible_keys:可能的索引
  • key:实际使用的索引
  • key_length:索引长度
  • ref:引用的列
  • rows_examined_per_scan:估计扫描的行数
  • rows_produced_per_join:估计连接产生的行数
  • filtered:过滤比例
  • using_index:是否使用索引

TREE格式执行计划

优势

  • 更直观的树状结构
  • 显示操作的层次关系
  • 更易于理解复杂查询

示例

sql
EXPLAIN FORMAT=TREE SELECT * FROM users WHERE id = 1;

结构

  • 根节点:整个查询
  • 子节点:各个操作
  • 显示操作的顺序和依赖关系

执行计划优化建议

优化全表扫描

  • 添加适当的索引
  • 优化WHERE条件
  • 考虑分区表

优化索引使用

  • 创建合适的复合索引
  • 避免索引失效的情况:
    • 使用函数操作索引列
    • 使用不等于操作符
    • 使用IS NULL或IS NOT NULL
    • 使用LIKE '%xxx'
    • 使用OR连接条件
    • 类型转换

优化排序和分组

  • 添加适当的索引支持排序
  • 避免使用SELECT *,只选择需要的列
  • 考虑使用覆盖索引

优化连接查询

  • 为连接列添加索引
  • 小表驱动大表
  • 考虑使用STRAIGHT_JOIN指定连接顺序

优化子查询

  • 考虑使用JOIN替代子查询
  • 避免相关子查询
  • 考虑使用临时表

工具与辅助

Performance Schema

  • 收集执行计划的实际执行数据
  • 与EXPLAIN的估计值对比

Sys Schema

  • 提供更友好的视图
  • 简化性能分析

第三方工具

  • pt-visual-explain:将执行计划可视化
  • MySQL Workbench:图形化执行计划分析
  • phpMyAdmin:Web界面查看执行计划

脚本自动化

  • 编写脚本自动分析慢查询日志中的执行计划
  • 定期生成执行计划报告
  • 监控执行计划的变化

常见问题(FAQ)

Q1: EXPLAIN显示的行数是精确值吗?

A1: 不是,EXPLAIN显示的rows值是MySQL基于统计信息估算的行数,不是实际执行时的精确行数。统计信息可能会过时,导致估算不准确。可以使用ANALYZE TABLE命令更新表的统计信息,提高估算的准确性。

Q2: 为什么EXPLAIN显示可能使用的索引,但实际没有使用?

A2: 可能的原因:

  • MySQL认为全表扫描比使用索引更快(例如表很小)
  • 索引选择性差,使用索引的成本高于全表扫描
  • 统计信息不准确,导致MySQL做出错误的选择
  • 查询条件不符合索引使用的要求

Q3: 如何优化EXPLAIN中显示的Using temporary和Using filesort?

A3: 优化方法:

  • 添加适当的索引,让排序和分组操作使用索引
  • 调整ORDER BY和GROUP BY的顺序,使其与索引顺序一致
  • 减少SELECT的列数,使用覆盖索引
  • 对于复杂查询,考虑拆分为多个简单查询

Q4: 如何理解EXPLAIN中的key_len字段?

A4: key_len表示使用的索引长度,单位是字节。它反映了索引的使用情况:

  • 对于复合索引,key_len越大,表示使用的索引列越多
  • 可以通过key_len判断是否使用了复合索引的全部列
  • key_len的计算考虑了列类型、长度和是否允许NULL

Q5: MySQL 8.0的执行计划有哪些新特性?

A5: MySQL 8.0的执行计划新特性:

  • 新增TREE格式,提供更直观的树状结构
  • JSON格式更加详细,包含更多执行信息
  • 支持EXPLAIN ANALYZE,执行查询并显示实际执行计划
  • 改进了索引使用的分析能力
  • 提供更多关于查询优化的信息