让MySQL更快:EXPLAIN语句详尽解析

前言

在数据库性能调优中,SQL 查询的执行效率是影响系统整体性能的关键因素之一。MySQL 提供了强大的工具------EXPLAIN 语句,帮助开发者和数据库管理员深入分析查询的执行计划,从而发现潜在的性能瓶颈并进行针对性优化。

EXPLAIN 语句能够模拟 MySQL 优化器的执行过程,返回查询的详细执行计划,包括表的访问顺序、索引的使用情况、连接类型、扫描行数等关键信息。通过理解 EXPLAIN 的输出,开发者可以快速定位低效查询的问题所在,例如全表扫描、缺少索引、临时表或文件排序等,并采取相应的优化措施。

本文将详细介绍 EXPLAIN 的基本用法、输出字段的含义,并通过实际案例演示如何利用 EXPLAIN 分析和优化 SQL 查询。

一、关于EXPLAIN语句

1.1 简介

EXPLAIN 是 MySQL 提供的用于分析 SQL 查询执行计划的工具。它通过在 SELECT 语句前添加 EXPLAIN 关键字,使 MySQL 返回查询的执行计划,而不是实际执行查询。执行计划描述了 MySQL 如何访问表、如何使用索引以及如何连接表等信息。

EXPLAIN 的主要作用包括:

  • 分析查询性能:识别慢查询的根源,例如全表扫描或索引未命中。
  • 验证索引有效性:确认是否正确使用了索引,或者是否需要添加新的索引。
  • 优化查询结构:调整查询语句或表结构以提高执行效率。

1.2 语法

EXPLAIN 的基本语法如下:

sql 复制代码
EXPLAIN [EXTENDED] [FORMAT = {TRADITIONAL | JSON}] SELECT ...;  
  • EXTENDED:扩展输出,显示更多信息(如优化后的查询语句)。
  • FORMAT = JSON:以 JSON 格式返回结果,便于解析和调试。

示例

在select前加explain关键字,MySQL会返回该查询的执行计划而不是执行这条SQL

sql 复制代码
mysql> explain select * from student where id=1;
+----+-------------+---------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table   | type  | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+---------+-------+---------------+---------+---------+-------+------+-------+
|  1 | SIMPLE      | student | const | PRIMARY       | PRIMARY | 4       | const |    1 | NULL  |
+----+-------------+---------+-------+---------------+---------+---------+-------+------+-------+
1 row in set

二、Explain列的含义

EXPLAIN 的输出结果包含多个字段,每个字段提供了不同的信息。以下是关键字段的详细说明:

2.1 概览

以下是 MySQL EXPLAIN输出列的详细说明,包括每列的定义、示例值、优化目标及判断标准,帮助你深入理解查询执行计划:

列名 作用描述 优化目标(好的输出特征)
id 查询的执行顺序标识符 子查询或复杂查询时,id 值高的先执行
select_type 查询类型(简单查询、子查询、UNION 等) SIMPLE​ 或 PRIMARY​ 表示简单或主查询
table 当前操作的表名(包括临时表或派生表) 表名清晰,避免过多 <derived>​ 或 <union>
partitions 匹配的分区(若表有分区) 分区裁剪合理,避免全分区扫描
type 数据访问方式(关键性能指标) const​、eq_ref​、ref​、range​ 优于 ALL
possible_keys 可能用到的索引 包含实际使用的索引
key 实际使用的索引 明确显示有效索引名,非 NULL
key_len 索引使用的字节数 与索引字段长度匹配,避免未完全使用索引
ref 与索引比较的列或常量 const​ 或关联字段,避免 NULL
rows 预估扫描的行数(估算值) 数值越小越好
filtered 查询条件过滤后剩余行的百分比 百分比高(接近 100%)
Extra 额外执行信息(关键优化提示) 出现 Using index​,避免 Using filesort

2.2 每列详细说明及优化建议

1. id

  • 含义 :查询的标识符,表示查询中 SELECT​ 子句的执行顺序。

    查询的序列号,标识执行顺序。相同 id​ 按从上到下执行;不同 id​ 时,值大的先执行(如子查询)。

  • 示例

    sql 复制代码
    EXPLAIN SELECT * FROM (SELECT * FROM t1) AS t_derived JOIN t2 ON t1.id = t2.id;
    • id=1:派生表 t_derived(子查询)。
    • id=1:主查询 t2
  • 优化目标

    避免多层嵌套子查询(id​ 过多),减少复杂查询。

  • 单一查询(无子查询或 UNION​)时,id​ 为 1​。

  • 复杂查询中,id​ 的层级清晰,避免嵌套过深。

2. select_type

  • 含义:查询的类型,描述查询的复杂度。

  • |常见值及优化建议:|||

    说明 理想情况
    SIMPLE 简单查询,不包含子查询或 UNION​。 最佳,避免复杂嵌套。
    PRIMARY 最外层查询。 正常,需关注其依赖的子查询。
    SUBQUERY 子查询中的第一个 SELECT​。 尽量避免,可考虑改写为连接查询。
    DEPENDENT SUBQUERY 子查询依赖外部查询结果。 高风险,可能导致性能下降。
    UNION UNION​ 中的第二个或后续查询。 正常,需注意 UNION​ 结果集。
    UNION RESULT UNION​ 的结果集。 正常,需检查是否需要额外处理。
    DERIVED 派生表(FROM​ 子句中的子查询)。 需检查派生表的性能。
    MATERIALIZED 物化子查询(MySQL 8.0+)。 正常,但需确认物化效果。

3. table

  • 含义:当前查询涉及的表名。

  • 理想输出

    • 表名明确,避免派生表(如 <derivedN>)或临时表(如 <union1,2>)。
    • 若出现派生表,需检查子查询是否可优化为连接查询。

4. partitions

  • 含义:查询涉及的分区(如果表是分区表)。

    表示查询涉及的分区情况。当表是分区表时,这个列会显示匹配的分区。例如,一个表按照日期字段进行分区,查询中指定了日期范围,那么 partitions 列就会显示涉及到的分区编号或者分区名称。

  • 理想输出

    • 分区表中仅扫描相关分区(如 p1​),而非全表扫描。

    • 若为 NULL​,表示表未分区或未使用分区。

      如果表是分区表,希望 partitions 列显示的分区范围尽量小。这样可以减少查询需要扫描的数据量,提高查询效率。例如,如果一个分区表有 100 个分区,而查询只涉及到其中的 1 - 2 个分区,这就是比较理想的输出。

5. type

  • 含义:连接类型(访问方法),反映 MySQL 如何查找表中的行。

  • 性能排序(从优到劣)

    1. system :表仅一行(系统表),是 const 的特例。
    2. const:通过主键或唯一索引等值查询,最多匹配一行。
    3. eq_ref :使用主键或唯一索引进行等值连接(如 JOIN)。
    4. ref:使用非唯一索引进行等值查询。
    5. range :索引范围查询(如 BETWEEN><)。
    6. index:全索引扫描(比全表扫描快)。
    7. ALL:全表扫描(最差)。
  • 优化建议

    • 目标是达到 consteq_refref
    • 避免 ALL,需添加索引或优化查询条件。

6. possible_keys

  • 含义:可能使用的索引(候选索引)。

  • 理想输出

    • 显示多个候选索引(说明索引设计合理)。

    • 若为 NULL​,表示无可用索引,需添加索引。

      列出与查询条件相关的索引。

7. key

  • 含义 :实际使用的索引。如果 key​ 为 NULL​,表示没有使用索引,可能是全表扫描。

  • 理想输出

    • 明确显示使用的索引(如 idx_name)。
    • 若为 NULL,表示未使用索引,需检查 possible_keys 并优化索引。
    • 显示与 possible_keys 中相同的索引,说明 MySQL 选择了合适的索引。

8. key_len

  • 含义:使用的索引长度(字节数)。

  • 理想输出

    • 值越小越好(表示使用的索引列越少或数据类型更紧凑)。
    • 例如,VARCHAR(100) 使用 utf8mb4 编码时,最大占用 400 字节。

9. ref

  • 含义:显示索引的哪一列被使用,以及与之比较的值(常量或列名)。

    • 显示哪些列或常量被用于查找索引列上的值。常见值包括:

    const​:使用常量值。

    表的列名:使用其他表的列进行比较。

  • func​:使用函数结果。

  • 理想输出

    • 显示具体的列名或常量(如 const),表明索引有效。
    • 若为 funcNULL,可能表示索引未正确使用。
    • 显示具体的列名或常量,表明索引被有效利用。

10. rows(估计扫描行数)

  • 含义:MySQL 估计需要扫描的行数。

  • 理想输出

    • 值越小越好(表示过滤条件越精确)。例如,如果一个查询估算只需要检查 10 行就可以得到结果,这比估算检查 10000 行要好得多。这表明查询能够快速定位到所需的数据行。
    • 若值过大(如 100000),需优化索引或查询条件。

11. filtered

  • 含义:表示查询条件过滤的行百分比(MySQL 5.7+)。该值表示查询扫描的行中有多少被筛选掉,值的范围是 0 到 100。

    • 表示在存储引擎返回的行中,经过 MySQL 服务器层过滤后,实际满足查询条件的行的比例。它是基于表统计信息和索引统计信息的一个估算值。
  • 理想输出

    • 值越高越好(如 100% 表示无过滤条件)。
    • 若值较低(如 10%),说明查询条件未充分利用索引。
    • filtered 的值应该尽可能高。例如,如果 filtered 的值是 90%,意味着存储引擎返回的行中有 90% 的行满足查询条件,这比 filtered 值为 10% 的情况要好,因为减少了不必要的数据处理。

12. Extra

  • 含义:额外信息,提供查询执行的附加说明,帮助诊断查询执行的细节

  • |常见值及优化建议 :|||

    说明 优化建议
    Using index 使用覆盖索引(查询列全部命中索引)。 无需回表,性能最佳。
    Using where 使用 WHERE​ 条件过滤数据。 正常,但需检查过滤条件效率。
    Using temporary 需要创建临时表(如 ORDER BY​ 和 GROUP BY​ 一起使用)。 避免,优化查询或添加索引。
    Using filesort 需要额外排序操作(如 ORDER BY​ 未使用索引)。 避免,优化排序字段索引。
    Distinct 优化了 DISTINCT​ 查询。 正常,无需额外优化。
    Range checked for each record 未找到合适索引,需逐行检查。 添加合适索引。

  • 良好输出:希望出现像 "Using index" 这样的提示,这表明查询效率较高。尽量避免出现 "Using temporary" 和 "Using filesort",因为它们表示需要额外的资源开销来处理查询,如临时表和文件排序,这可能会降低查询性能。

三、优化建议及示例

3.1 优化建议

  1. 关注 type :确保查询达到 consteq_refref 级别,避免 ALL
  2. 优化 Extra :避免 Using filesortUsing temporary
  3. 分析 key possible_keys:确认是否使用了预期的索引。
  4. 减少扫描行数 :通过索引或优化查询条件降低 rows 值。
  5. 检查 filtered:确保过滤条件有效,提高查询效率。

3.2 优化示例

全表扫描

场景 :全表扫描(type=ALL​)

  • 问题 SQL

    sql 复制代码
    EXPLAIN SELECT * FROM users WHERE phone = '123456789';
  • 输出type=ALL​,key=NULL​。

  • 优化 :为 phone​ 字段添加索引:

    sql 复制代码
    ALTER TABLE users ADD INDEX idx_phone(phone);
  • 优化后输出type=ref​,key=idx_phone​,rows=1​。

多表连接优化(Using join buffer)

问题描述

多表连接时出现 Using join buffer​,性能低下。

原SQL

sql 复制代码
SELECT u.name, o.order_no 
FROM users u 
JOIN orders o ON u.id = o.user_id 
WHERE u.age > 25 AND o.status = 'completed';

EXPLAIN 分析

sql 复制代码
EXPLAIN SELECT u.name, o.order_no FROM users u JOIN orders o ON u.id = o.user_id WHERE u.age > 25 AND o.status = 'completed';

输出结果

复制代码
id | select_type | table | type | possible_keys | key        | rows | Extra
---|-------------|-------|------|---------------|------------|------|-------------------
1  | SIMPLE      | u     | ALL  | idx_age       | NULL       | 1000 | Using where
1  | SIMPLE      | o     | ref  | idx_user_id   | idx_user_id| 500  | Using where; Using join buffer (Block Nested Loop)

问题诊断

  • users 表 type=ALL:未使用索引,全表扫描。
  • orders 表 Using join buffer:连接时未使用索引,性能差。

优化方案

  1. users.age创建索引

    sql 复制代码
    ALTER TABLE users ADD INDEX idx_age (age);
  2. orders.user_id orders.status创建索引

    sql 复制代码
    ALTER TABLE orders ADD INDEX idx_user_id_status (user_id, status);
  3. 调整查询

    sql 复制代码
    SELECT u.name, o.order_no 
    FROM users u 
    JOIN orders o ON u.id = o.user_id 
    WHERE u.age > 25 AND o.status = 'completed';
  4. 验证优化效果

    sql 复制代码
    EXPLAIN SELECT u.name, o.order_no FROM users u JOIN orders o ON u.id = o.user_id WHERE u.age > 25 AND o.status = 'completed';

    优化后输出

    复制代码
    id | select_type | table | type | possible_keys         | key                  | rows | Extra
    ---|-------------|-------|------|-----------------------|----------------------|------|---------
    1  | SIMPLE      | u     | range| idx_age               | idx_age              | 500  | Using where
    1  | SIMPLE      | o     | ref  | idx_user_id_status    | idx_user_id_status   | 200  | Using where

效果

  • users 表 type=range:使用索引范围扫描。
  • orders 表 Using join buffer 消失:连接直接通过索引完成。

结束语

EXPLAIN​语句是MySQL查询优化的核心工具,如同数据库工程师的"听诊器"。通过本文的详细解析,相信您已经掌握了各输出列的精髓。但需要强调的是,真正的优化功力需要在实践中不断积累。建议每次执行重要查询时养成查看执行计划的习惯,结合业务场景灵活运用索引策略、查询重写等手段。记住:优秀的数据库性能不是偶然,而是源于对每个执行细节的精心雕琢。

相关推荐
Alfadi联盟 萧瑶8 分钟前
数据库与编程安全
数据库·安全
weixin_4708802621 分钟前
InnoDB引擎底层解析(二)之InnoDB的Buffer Pool(三)
数据库·程序人生·mysql·面试·sql优化·存储引擎
君的名字22 分钟前
怎么判断一个Android APP使用了flutter 这个跨端框架
android·flutter
PWRJOY24 分钟前
Flask-SQLAlchemy数据库查询:query
数据库·flask·sqlalchemy
Allen_LVyingbo27 分钟前
传统医疗系统文档集中标准化存储和AI智能化更新路径分析
数据库·人工智能
hello1114-30 分钟前
Redis学习打卡-Day6-Redis 高可用(上)
数据库·redis·学习
luckyext1 小时前
SQL SERVER常用聚合函数整理及示例
运维·服务器·数据库·sql·mysql·sqlserver·mssql
淡淡的香烟1 小时前
Android12 launcher3修改App图标白边问题
android
时序数据说1 小时前
时序数据库IoTDB的分片与负载均衡策略深入解析
大数据·数据库·开源·负载均衡·时序数据库·iotdb
怡雪~1 小时前
redis的主从复制
数据库·redis·缓存