游乐游手机版
首页/数据库/文章详情

MySQL EXPLAIN用法实例深度详解与数据库性能调优实战

时间:2026-06-13 06:55
一、认识 EXPLAIN:MySQL SQL 调优的核心分析工具在 SQL 调优过程中,EXPLAIN 可以说是不可或缺的核心工具。其本质是 MySQL 内置的执行计划分析器,能够直观展示 SQL 语句的详细执行流程。通过 EXPLAIN,我们可以清晰地了解 MySQL 查询优化器如何执行该 SQL

一、认识 EXPLAIN:MySQL SQL 调优的核心分析工具

在 SQL 调优过程中,EXPLAIN 可以说是不可或缺的核心工具。其本质是 MySQL 内置的执行计划分析器,能够直观展示 SQL 语句的详细执行流程。通过 EXPLAIN,我们可以清晰地了解 MySQL 查询优化器如何执行该 SQL——包括表的读取顺序、索引使用情况、预估扫描行数等关键指标。掌握这些信息后,定位并优化性能瓶颈将不再无从下手。

MySQL EXPLAIN用法实例深度详解

需要说明的是,本文内容主要适用于 MySQL 5.7 及以上版本,以及 8.0 版本。其中 EXPLAIN ANALYZE 功能需要 MySQL 8.0.18+,Hash Join 特性需要 MySQL 8.0.20+。

二、EXPLAIN 基本语法与用法

2.1 标准用法

EXPLAIN SELECT * FROM table_name WHERE condition;

使用方式极为简便,只需在待分析的 SQL 语句前加上 EXPLAIN 关键字即可。

2.2 支持的语句类型

需要注意的是,EXPLAIN 不仅适用于 SELECT 查询,以下类型的语句同样可以进行分析:

  • SELECT
  • DELETE
  • INSERT
  • REPLACE
  • UPDATE

2.3 MySQL 8.0+ 新增用法

-- 实际执行并分析耗时(MySQL 8.0.18+)
EXPLAIN ANALYZE SELECT * FROM users WHERE age = 25;
-- JSON 格式输出(包含成本模型数据)
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE age = 25;

EXPLAIN ANALYZE 是 MySQL 8.0 引入的重磅特性,它会实际执行 SQL 并返回真实耗时与扫描行数,相比普通 EXPLAIN 的估算值更具参考价值。

三、EXPLAIN 输出字段详细解读

跑完 EXPLAIN 后,MySQL 会返回一个结果集,包含下面这些列:

字段

含义

id

查询标识符,表示执行顺序

select_type

查询类型(SIMPLE、PRIMARY、SUBQUERY 等)

table

访问的表名

partitions

匹配的分区(MySQL 5.7+)

type

访问类型(ALL、index、range、ref、eq_ref 等)

possible_keys

可能使用的索引

key

实际使用的索引

key_len

使用的索引长度

ref

与索引比较的列或常量

rows

估算需要扫描的行数

filtered

按条件过滤后剩余行的百分比(MySQL 5.7+)

Extra

额外信息(非常重要)

在众多字段中,最值得重点关注的是 typekeyrowsExtra 四列。接下来我们将逐一深入解析。

四、核心字段深度解析与优化指南

4.1 id 列 - 执行顺序标识

规则

  • id 相同:从上往下顺序执行
  • id 不同:id 值越大,优先级越高,越先执行
  • id 为 NULL:最后执行(通常是 UNION 结果合并)
-- 示例:子查询
EXPLAIN SELECT * FROM test1 WHERE id IN (SELECT id FROM test2);
-- 结果中 id=2 的子查询会先执行,id=1 的主查询后执行

简而言之,id 列反映了查询的执行路线图。

4.2 select_type 列 - 查询类型

类型

说明

SIMPLE

简单查询,不包含子查询或 UNION

PRIMARY

最外层查询

SUBQUERY

SELECT 或 WHERE 中的子查询

DERIVED

FROM 中的子查询(派生表)

UNION

UNION 中的第二个及后续查询

UNION RESULT

UNION 结果合并

日常开发中最常见的是 SIMPLE 类型,但当 SQL 包含子查询或 UNION 时,其他类型便会发挥作用。

4.3 type 列 - 访问类型(性能关键)

type 列是 EXPLAIN 输出中最关键的字段之一,直接表明 MySQL 如何定位目标数据行。不同访问类型性能差异显著,从最优到最差排序如下:

类型

说明

system

不进行磁盘IO,查询系统表,仅仅返回一条数据

const

通过主键或唯一索引一次就找到

eq_ref

连接查询中,被驱动表使用主键/唯一索引等值匹配

ref

使用普通索引等值匹配

range

索引范围扫描(BETWEEN、IN、>、< 等)

index

遍历整颗索引树,比ALL快一些,因为索引文件要比数据文件小

ALL

全表扫描

优化建议:上线前检查一下,至少得保证 range 级别,最好能达到 refeq_ref。如果看到 type=ALL,那基本就是告警信号,说明查询在走全表扫描。

4.4 Extra 列 - 额外信息

这是藏得最深的“情报站”。很多麻烦都是从 Extra 里暴露出来的:

含义

优化建议

Using index

使用覆盖索引

理想状态,无需回表

Using where

使用 WHERE 过滤(全表扫描或者在查找使用索引的情况下,但是还有查询条件不在索引字段当中)

正常情况

Using filesort

使用外部排序(无法利用索引排序)

需要优化,考虑添加索引

Using temporary

使用临时表来存储结果集,常见于排序和分组查询

常见于 GROUP BY / ORDER BY,需优化

Using join buffer

使用连接缓存

连接条件未使用索引

Impossible WHERE

WHERE 条件永远为 false

检查逻辑

Select tables optimized away

优化器确定最多返回一行

无需优化

特别需要警惕的是 Using filesortUsing temporary,一旦出现这两个提示,通常意味着索引设计或 SQL 编写存在隐患,亟需优化。

五、实战案例详解

5.1 单表查询分析

-- 表结构:users(id, age, score, name, address)
-- 索引:idx_age_score_name(age, score, name)
EXPLAIN SELECT * FROM users WHERE age = 25;

结果分析

+----+-------------+-------+------------+------+---------------------+---------------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys       | key                 | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------------+---------------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | users | NULL       | ref  | idx_age_score_name  | idx_age_score_name  | 5       | const |   12 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------------+---------------------+---------+-------+------+----------+-------------+

结果解读

  • type=ref:使用了普通索引等值匹配,性能良好
  • key=idx_age_score_name:实际启用了联合索引
  • Extra=Using index:实现了覆盖索引,无需回表查询
  • rows=12:仅扫描 12 行,查询非常高效

5.2 连接查询分析

EXPLAIN SELECT * FROM test1 t1 INNER JOIN test2 t2 ON t1.id = t2.id;

关键观察点

  • 注意识别驱动表——通常 rows 较小的表作为驱动表性能更优
  • 被驱动表的 type 应达到 eq_ref,说明使用了主键或唯一索引进行匹配

5.3 使用 EXPLAIN ANALYZE(MySQL 8.0.18+)

EXPLAIN ANALYZE SELECT * FROM users WHERE age = 25\G

输出示例

*************************** 1. row ***************************
EXPLAIN: -> Covering index lookup on users using idx_age_score_name (age=25)
(cost=1.52 rows=12) (actual time=0.0272..0.0344 rows=12 loops=1)

核心优势

  • 直接展示实际执行时间(actual time)
  • 显示实际返回行数(rows)
  • 数据真实可靠,比普通 EXPLAIN 的估算值更具说服力

六、常见 SQL 优化场景与解决方案

6.1 避免全表扫描(type = ALL)

问题诊断

  • 查询条件列没有索引
  • 查询写入了函数导致索引失效
  • 多表 JOIN 时驱动表选错了

优化方法

-- 错误:函数包装导致索引失效
SELECT * FROM orders WHERE YEAR(order_date) = 2023;
-- 正确:改写为范围查询
SELECT * FROM orders WHERE order_date >= '2023-01-01' AND order_date < '2024-01-01';

开发者常犯的错误是对索引列使用函数,导致索引失效。将其改写为范围查询后,性能往往能得到显著提升。

6.2 消除文件排序(Using filesort)

-- 添加合适的索引避免 filesort
CREATE INDEX idx_age_name ON users(age, name);
-- 查询同时满足 WHERE 和 ORDER BY
SELECT * FROM users WHERE age > 20 ORDER BY age, name;

文件排序意味着查询无法借助索引完成排序,需在内存或磁盘中额外进行排序操作。通过创建合适的联合索引,即可有效避免文件排序。

6.3 利用覆盖索引

-- 索引:idx_age_name(age, name)
-- ✅ 覆盖索引查询(Extra = Using index)
EXPLAIN SELECT age, name FROM users WHERE age = 25;
-- ❌ 非覆盖索引(需要回表查询)
EXPLAIN SELECT * FROM users WHERE age = 25;

如果查询仅需 agename 两列,且索引已包含它们,则查询可直接在索引树完成,无需回表,效率大幅提升。

七、EXPLAIN 的局限性

不过,EXPLAIN 也不是万能的,它有一些内在限制需要了解:

  1. 无法提供触发器、存储过程等内部执行信息
  2. 不考虑查询缓存等各类缓存机制
  3. 不显示 MySQL 执行查询时内部所做的优化
  4. 部分统计信息为估算值,并非精确数据
  5. 标准 EXPLAIN 不会实际执行 SQL(EXPLAIN ANALYZE 除外)

因此,EXPLAIN 是一个优秀的分析工具,但最终的性能判断仍需结合实际执行情况综合考量。

八、总结

检查项

优化目标

type

至少达到 range,最好 ref 或 eq_ref

key

确保实际使用了索引

rows

越小越好

Extra

避免出现 Using filesort、Using temporary

覆盖索引

尽量让 Extra 显示 Using index

掌握 EXPLAIN 是 SQL 优化的基本功和入门必修课。遇到慢 SQL 时,先执行 EXPLAIN,检查 type 是否低于 range、Extra 是否出现 filesort 或 temporary,然后根据这些线索优化索引及 SQL 写法。熟练运用这套流程后,解决性能瓶颈将变得更加高效。

来源:https://www.jb51.net/database/361683ytq.htm
上一篇Oracle RMAN备份至MinIO防止数据删除详细步骤 下一篇Windows下Oracle JDK 17.0.18手把手安装与环境变量配置保姆级教程
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
Redis 7.0增量AOF重写RDB前导码配置详解
数据库 · 2026-07-02

Redis 7.0增量AOF重写RDB前导码配置详解

先说一个几乎所有人都踩过的典型误区:很多人把 aof-use-rdb-preamble yes 当作开启“增量重写”的开关。实际上,这个配置只干了一件事——让重写后的 AOF 文件头部带上 RDB 快照。它解决的是加载速度问题,跟“增量重写”本身的概念压根不是一回事。真正的增量重写,依赖的是 Red

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践
数据库 · 2026-07-02

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践

直接在Tornado里用SQLAlchemy同步执行SQL,结果就是阻塞IOLoop,所谓“异步框架里写同步数据库代码”,等于白搭。安全执行的关键不是“怎么写SQL”,而是“怎么不卡住事件循环”。 为什么不能在RequestHandler里直接调用session execute() 因为sessio

利用SQL触发器实现在INSERT数据时自动同步到审计表
数据库 · 2026-07-02

利用SQL触发器实现在INSERT数据时自动同步到审计表

先说结论:可以用触发器把 INSERT 数据同步到审计表,但必须用 AFTER INSERT,并且审计表的字段顺序、类型、字符集得和源表严格一致。否则,轻则写入错位、数据截断,重则直接报错、丢数据。下面把这些坑一个一个掰开说。 能,但必须用 AFTER INSERT,且审计表字段顺序、类型、字符集要

如何用SQL编写按不同工作日统计员工出勤率
数据库 · 2026-07-02

如何用SQL编写按不同工作日统计员工出勤率

在实际业务中,统计不同工作日的出勤率是HR系统里的高频需求。如果直接按日期函数分组,很容易掉进语言环境、索引失效或分母口径的坑里。下面就来拆解具体的实现要点。 必须用 CASE WHEN 将日期映射为固定 weekday 标签(如 Mon )再分组,避免语言环境导致的分组断裂;需过滤 DOW IN

Spring Boot 3动态拼接SQL为何引发严重安全漏洞
数据库 · 2026-07-02

Spring Boot 3动态拼接SQL为何引发严重安全漏洞

SQL注入漏洞的核心成因,本质上是因为用户输入直接参与了SQL语句的字符串拼接,而未采用参数化绑定机制。在MyBatis中使用${}、QueryWrapper中调用apply()与last()、JPA的@Query注解进行拼接等操作,都会绕过PreparedStatement的安全防护。动态字段必须