一、前言
1.1 什么是explain
explain是mysql提供的sql执行计划分析命令,用于展示mysql优化器如何执行sql语句。通过explain可以分析索引使用情况、表连接顺序、扫描行数等关键信息,是sql性能优化的核心工具。
1.2 基础知识要求
- sql基础:了解基本的select、join语法
- 索引概念:熟悉单列索引、联合索引、覆盖索引
- 执行计划:理解mysql优化器的工作方式
二、explain的使用方法
2.1 基本语法
-- 分析select语句 explain select * from users where id = 1; -- 分析delete/update/insert(mysql 5.6+) explain delete from users where status = 'inactive'; -- 显示更详细信息(mysql 8.0.18+) explain analyze select * from users where id = 1; -- 实际执行并显示耗时 -- 输出json格式(便于程序解析) explain format=json select * from users where id = 1; -- 查看连接中正在执行的sql的执行计划 explain for connection 123; -- 123为connection_id
2.2 explain输出字段概览
explain select o.order_id, u.user_name from orders o left join users u on o.user_id = u.user_id where o.order_status = 'pending' and o.created_at >= '2024-01-01' limit 10\g
输出结果示例:
*************************** 1. row ***************************
id: 1
select_type: simple
table: o
partitions: null
type: range
possible_keys: idx_status_created,idx_created_at
key: idx_status_created
key_len: 102
ref: null
rows: 185000
filtered: 100.00
extra: using index condition
*************************** 2. row ***************************
id: 1
select_type: simple
table: u
partitions: null
type: eq_ref
possible_keys: primary
key: primary
key_len: 4
ref: test.o.user_id
rows: 1
filtered: 100.00
extra: null三、核心参数详解
3.1 id:查询标识符
| 值 | 含义 | 示例场景 |
|---|---|---|
| 相同id | 顺序执行,从上到下 | 多表join |
| 不同id | id越大越先执行(优先级更高) | 子查询、union |
示例:
-- id相同:多表join explain select * from orders o, users u where o.user_id = u.user_id; -- 结果:两个id均为1 -- id不同:子查询 explain select * from users where user_id in (select user_id from orders); -- 结果:子查询id=2,外层查询id=1(子查询先执行)
3.2 select_type:查询类型
| 类型 | 含义 | 优化关注点 |
|---|---|---|
| simple | 简单查询,不包含子查询或union | 最理想,无额外开销 |
| primary | 最外层查询 | 优化重点 |
| subquery | 子查询(非from子句) | 尽量减少子查询,可改写为join |
| derived | 派生表(from子句中的子查询) | 会生成临时表,注意性能 |
| union | union中的第二个或后续查询 | 每个union分支单独分析 |
| union result | union的结果集 | 合并结果的开销 |
| dependent subquery | 依赖外部查询的子查询 | ⚠️ 高危,每行外层数据都会执行一次 |
重点关注:
-- 避免dependent subquery(子查询依赖外层) explain select * from users u where (select count(*) from orders o where o.user_id = u.user_id) > 5; -- 优化:改为join + group by -- 注意derived临时表开销 explain select * from (select * from orders where status='pending') t; -- 建议:直接查询,或创建视图
3.3 type:访问类型(最重要指标之一)
性能从优到劣排序:
system > const > eq_ref > ref > range > index > all
| 类型 | 说明 | 示例 | 优化目标 |
|---|---|---|---|
| system | 系统表,只有一行数据 | 极少出现 | ✅ 最优 |
| const | 主键或唯一索引等值查询,最多返回一行 | where id = 1 | ✅ 理想 |
| eq_ref | 连接查询时,被驱动表使用主键或唯一索引 | join + 主键关联 | ✅ 优秀 |
| ref | 使用非唯一索引等值查询 | where status = 'pending' | ✅ 良好 |
| range | 索引范围扫描 | where id > 100、between、in | ✅ 可接受 |
| index | 全索引扫描(扫描整个索引树) | 覆盖索引但无where条件 | ⚠️ 需优化 |
| all | 全表扫描 | 无索引或索引失效 | ❌ 必须优化 |
实战对比:
-- const:最优 explain select * from users where user_id = 1; -- type=const -- ref:良好 explain select * from orders where order_status = 'pending'; -- type=ref(假设status有索引) -- range:可接受 explain select * from orders where created_at >= '2024-01-01'; -- type=range(假设created_at有索引) -- all:必须优化 explain select * from orders where order_status = 'pending'; -- type=all(status无索引)
3.4 possible_keys:可能使用的索引
| 值 | 含义 | 处理建议 |
|---|---|---|
| 有索引名 | 优化器可能使用这些索引 | 正常 |
| null | 没有可用的索引 | 考虑添加索引 |
注意:possible_keys有值不代表实际使用,需结合key字段判断。
3.5 key:实际使用的索引
| 值 | 含义 | 优化建议 |
|---|---|---|
| 有索引名 | 实际使用了该索引 | 良好 |
| null | 未使用索引(全表扫描或全索引扫描) | 需要优化 |
| primary | 使用了主键索引 | 最优 |
关键场景:
-- possible_keys有值但key为null:索引未被使用 explain select * from orders where date(created_at) = '2024-01-01'; -- possible_keys: idx_created_at, key: null -- 原因:对索引字段使用了函数,索引失效 -- 实际使用了联合索引 explain select * from orders where order_status = 'pending' and created_at > '2024-01-01'; -- key: idx_status_created(联合索引)
3.6 key_len:使用的索引字节长度
作用:
- 判断联合索引中实际使用了哪些字段
- 长度越长,表示使用的索引字段越多
计算规则(以innodb为例):
| 数据类型 | 长度计算 |
|---|---|
tinyint | 1字节 |
int | 4字节 |
bigint | 8字节 |
varchar(100) | 100*3 + 2(utf8mb4)或 100 + 2(latin1) |
char(10) | 10*字符集字节数 |
| 允许null | 额外+1字节 |
实战分析:
-- 联合索引:idx_status_created (order_status varchar(20), created_at datetime) show index from orders; -- 查看索引定义 explain select * from orders where order_status = 'pending' and created_at >= '2024-01-01'; -- key_len = 83 -- 计算:order_status(20*3+2=62) + created_at(8) + null标志(1) = 71? 实际83包含额外开销 -- 如果只使用第一个字段 explain select * from orders where order_status = 'pending'; -- key_len = 62(仅使用了order_status字段)
3.7 ref:索引列的比较对象
| 值 | 含义 | 示例 |
|---|---|---|
| const | 与常量比较 | where id = 1 |
| 表名.字段 | 与其他表字段比较 | join条件 |
| null | 非等值查询或未使用索引 | where id > 10 |
示例:
explain select * from orders o, users u where o.user_id = u.user_id and o.order_status = 'pending'; -- table=o 的 ref: const (order_status='pending') -- table=u 的 ref: test.o.user_id (关联到orders表的user_id)
3.8 rows:预估扫描行数
含义:mysql优化器预估需要读取的行数(非精确值)
重要性:
- 核心优化指标,与查询耗时正相关
- 目标:
rows尽可能小 - 若
rows接近表总行数,说明索引效果差
实战:
-- 全表扫描:rows ≈ 表总行数 explain select * from orders where order_status = 'pending'; -- rows: 5,000,000(全表) -- 添加索引后:rows大幅下降 explain select * from orders where order_status = 'pending'; -- rows: 250,000(索引过滤后)
3.9 filtered:过滤后剩余行数百分比
含义:满足where条件的行数占rows的预估百分比
计算:实际返回行数 ≈ rows × filtered%
重要性:
filtered越低,说明where条件过滤效果好- 低
filtered但rows大时,需要更精准的索引
示例:
explain select * from orders where order_status = 'pending' and user_id = 100; -- rows: 250000, filtered: 1.00 -- 实际返回行数 ≈ 2500(过滤掉了99%的数据) -- 优化:创建(status, user_id)联合索引 -- 优化后 rows: 50, filtered: 100.00
3.10 extra:额外信息(重要优化线索)
| 值 | 含义 | 优化建议 |
|---|---|---|
| using index | 覆盖索引,不需要回表 | ✅ 最优状态 |
| using index condition | 索引下推(icp) | ✅ 良好,mysql 5.6+优化 |
| using where | 使用where过滤(在server层) | ⚠️ 可接受,但索引层过滤更优 |
| using filesort | 需要额外排序,无法利用索引 | ❌ 需优化,添加排序字段索引 |
| using temporary | 使用临时表(group by/union/distinct) | ❌ 需优化,通常是性能杀手 |
| using join buffer | 连接缓冲区(block nested loop) | ⚠️ 被驱动表缺少索引 |
| using index for group-by | 使用索引优化group by | ✅ 良好 |
| impossible where | where条件永远为假 | 检查业务逻辑 |
| no tables used | 没有from子句或from dual | 正常 |
重点优化场景:
场景1:using filesort
-- 问题sql explain select * from orders where status='pending' order by created_at desc; -- extra: using where; using filesort -- 优化:添加(status, created_at)联合索引 create index idx_status_created on orders(status, created_at); -- 优化后extra不再出现filesort
场景2:using temporary
-- 问题sql explain select status, count(*) from orders group by status order by count(*); -- extra: using temporary; using filesort -- 优化:拆分为两个查询,或调整group by/order by顺序
场景3:using index(覆盖索引)
-- 覆盖索引:查询字段都在索引中 explain select order_id, user_id from orders where order_id = 100; -- extra: using index(主键索引覆盖) -- 创建覆盖索引示例 create index idx_cover on orders(status, created_at, order_id); explain select status, created_at, order_id from orders where status='pending'; -- extra: using index(无需回表)
四、实战分析流程
4.1 标准分析步骤
1. 执行explain,获取执行计划 ↓ 2. 检查type:是否为all或index? ↓ 是 → 添加索引 ↓ 否 3. 检查key:是否为null? ↓ 是 → 分析索引失效原因 ↓ 否 4. 检查rows:是否过大? ↓ 是 → 优化索引选择性 ↓ 否 5. 检查extra:是否有using filesort/using temporary? ↓ 是 → 优化排序/分组索引 ↓ 否 6. 性能良好
4.2 综合案例分析
案例:订单报表查询慢
explain select
date(o.created_at) as order_date,
u.user_level,
count(*) as order_count,
sum(o.order_amount) as total_amount
from orders o
left join users u on o.user_id = u.user_id
where o.created_at >= '2024-01-01'
and o.created_at < '2024-02-01'
and o.order_status in ('paid', 'shipped')
group by date(o.created_at), u.user_level
order by order_date desc, u.user_level;explain结果:
| table | type | key | rows | extra |
|---|---|---|---|---|
| o | all | null | 5,234,567 | using where; using temporary; using filesort |
| u | eq_ref | primary | 1 | null |
问题诊断:
- type=all:orders全表扫描
- key=null:未使用索引
- rows=523万:扫描全部数据
- using temporary:group by产生临时表
- using filesort:order by需要额外排序
优化方案:
-- 1. 创建联合索引
create index idx_status_created on orders(order_status, created_at);
-- 2. 改写sql,避免date()函数
select
date(o.created_at) as order_date,
u.user_level,
count(*) as order_count,
sum(o.order_amount) as total_amount
from orders o
left join users u on o.user_id = u.user_id
where o.created_at >= '2024-01-01'
and o.created_at < '2024-02-01'
and o.order_status in ('paid', 'shipped')
group by date(o.created_at), u.user_level
order by order_date desc, u.user_level;
-- 3. 考虑使用汇总表(物化视图)预处理五、explain analyze(mysql 8.0.18+)
5.1 功能说明
实际执行sql并返回详细的执行统计信息,包括实际耗时、实际行数等,比explain更精确。
explain analyze select * from orders where order_status = 'pending'\g
输出示例:
-> filter: (orders.order_status = 'pending') (cost=101.23 rows=1850) (actual time=0.123..0.456 rows=1234 loops=1)
-> index lookup on orders using idx_status (order_status='pending') (cost=101.23 rows=1850) (actual time=0.098..0.234 rows=1234 loops=1)关键信息:
actual time:实际执行时间rows:实际返回行数loops:循环执行次数(被驱动表)
六、优化检查清单
| 检查项 | 理想状态 | 问题信号 |
|---|---|---|
| type | const/eq_ref/ref/range | all/index |
| key | 有索引名 | null |
| rows | < 1000或占总行数<5% | 接近表总行数 |
| extra | using index | using filesort/using temporary |
| filtered | 高(>30%) | 低(<5%)但rows大 |
七、学习建议
- 熟记type优先级:const > eq_ref > ref > range > index > all
- 重点关注extra:filesort和temporary是常见性能杀手
- 结合业务验证rows:预估扫描行数是否合理
- 善用show index:了解表索引结构后再分析
- mysql 8.0用explain analyze:获取实际执行统计
- 建立知识库:记录常见问题模式(函数导致索引失效、隐式类型转换等)
到此这篇关于mysql explain 关键参数详细解释的文章就介绍到这了,更多相关mysql explain 关键参数内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论