深度解析MySQL EXPLAIN:揭秘SQL执行计划的每个细节

作为MySQL性能调优的核心工具,EXPLAIN命令能帮助我们理解SQL语句的执行计划。本文将全面解析EXPLAIN输出的每个字段及其可能的值,并通过实际场景说明如何利用这些信息优化查询性能。

EXPLAIN基础语法

EXPLAIN SELECT * FROM users WHERE id = 1;
-- 或更详细的格式
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE id = 1;

核心字段解析

1. id - 查询标识符

表示SELECT查询的序列号,有几个SELECT就有几个id。

​重要特性​​:

  • id相同:执行顺序从上到下
  • id不同:数值越大越先执行
  • id为NULL:表示结果集(如UNION结果)

2. select_type - 查询类型

描述 场景示例
SIMPLE 简单查询 不含子查询或UNION的查询
PRIMARY 主查询 包含子查询的外层查询
SUBQUERY 子查询 SELECT列表或WHERE中的子查询
DERIVED 派生表 FROM子句中的子查询
UNION UNION中的第二个或后面的查询 SELECT...UNION SELECT...
UNION RESULT UNION的结果 UNION操作的结果集

3. table - 访问的表

显示查询涉及的表名,可能是:

  • 实际表名
  • (派生表,N是id)
  • (UNION结果)

4. partitions - 匹配的分区

显示查询将访问的分区,非分区表为NULL。

5. type - 访问类型(关键指标)

从最优到最差排序:

类型 描述 场景
system 系统表,只有一行 系统表查询
const 通过主键或唯一索引查找 WHERE id = 1
eq_ref 唯一索引关联 多表JOIN使用主键或唯一键
ref 非唯一索引查找 使用普通索引的查询
fulltext 全文索引 使用FULLTEXT索引
ref_or_null 类似ref,但包含NULL值 WHERE col = ... OR col IS NULL
index_merge 索引合并 多个索引条件OR组合
unique_subquery 子查询返回唯一值 WHERE col IN (SELECT primary_key FROM ...)
index_subquery 类似unique_subquery但返回不唯一 WHERE col IN (SELECT key FROM ...)
range 索引范围扫描 BETWEENIN><
index 全索引扫描 只需扫描索引无需回表
ALL 全表扫描 无合适索引或强制全表扫描

​性能提示​​:至少达到range级别,避免ALL。

6. possible_keys - 可能使用的索引

列出查询可能使用的索引,但不一定实际使用。

7. key - 实际使用的索引

查询实际使用的索引,NULL表示未使用索引。

8. key_len - 使用的索引长度

表示索引中使用的字节数,可用于判断使用了索引的哪些部分。

计算规则:

  • 4字节(INT)
  • 1字节(TINYINT)
  • 如果是NULLable字段,额外+1字节
  • 字符集相关(如utf8mb4=4字节/字符)

9. ref - 索引关联的列

显示索引查找时使用了哪些列或常量。

常见值:

  • const:常量值
  • 列名:关联查询中的列
  • func:函数结果

10. rows - 预估扫描行数

MySQL估计需要读取的行数(不是精确值)。

11. filtered - 条件过滤百分比

表示存储引擎返回的数据在服务层过滤后剩余的比例(0-100%)。

12. Extra - 额外信息(重要)

描述 优化建议
Using index 覆盖索引 良好,无需回表
Using where 服务层过滤 可能需要优化索引
Using temporary 使用临时表 考虑优化复杂GROUP BY
Using filesort 外部排序 需要优化ORDER BY
Select tables optimized away 优化器优化掉了表访问 极佳情况
Impossible WHERE WHERE条件永远为假 检查查询条件
Distinct 优化DISTINCT操作
Using join buffer 使用连接缓冲 可能需要优化JOIN

实战分析案例

案例1:基本查询分析

EXPLAIN SELECT * FROM orders WHERE user_id = 100;
+----+-------------+--------+------+---------------+---------+---------+-------+------+-------+
| id | select_type | table  | type | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+--------+------+---------------+---------+---------+-------+------+-------+
|  1 | SIMPLE      | orders | ref  | user_id       | user_id | 4       | const |    5 |       |
+----+-------------+--------+------+---------------+---------+---------+-------+------+-------+

分析:使用了user_id索引,类型为ref,预估扫描5行,性能良好。

案例2:需要优化的查询

EXPLAIN SELECT * FROM products WHERE category = 'electronics' ORDER BY price DESC;

可能输出:

+----+-------------+----------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table    | type | possible_keys | key  | key_len | ref  | rows | Extra          |
+----+-------------+----------+------+---------------+------+---------+------+------+----------------+
|  1 | SIMPLE      | products | ALL  | category      | NULL | NULL    | NULL | 5000 | Using filesort |
+----+-------------+----------+------+---------------+------+---------+------+------+----------------+

问题:全表扫描(ALL)和使用文件排序(Using filesort)。

优化方案:

ALTER TABLE products ADD INDEX (category, price);

案例3:复杂查询分析

EXPLAIN 
SELECT u.name, COUNT(o.id) 
FROM users u LEFT JOIN orders o ON u.id = o.user_id
WHERE u.create_time > '2023-01-01'
GROUP BY u.id
HAVING COUNT(o.id) > 5;

JSON格式输出分析要点:

{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "1257.80"
    },
    "nested_loop": [
      {
        "table": {
          "table_name": "u",
          "access_type": "range",
          "possible_keys": ["PRIMARY","create_time"],
          "key": "create_time",
          "rows_examined_per_scan": 342,
          "rows_produced_per_join": 342,
          "filtered": "100.00",
          "using_index": true
        }
      },
      {
        "table": {
          "table_name": "o",
          "access_type": "ref",
          "possible_keys": ["user_id"],
          "key": "user_id",
          "rows_examined_per_scan": 5,
          "rows_produced_per_join": 1710,
          "filtered": "100.00"
        }
      }
    ],
    "grouping_operation": {
      "using_temporary_table": true,
      "using_filesort": true,
      "cost_info": {
        "sort_cost": "1710.00"
      }
    }
  }
}

问题点:使用了临时表和文件排序。

优化方案:

  1. 确保users表的create_time有索引
  2. 考虑预聚合或缓存统计结果

高级技巧

1. 索引有效性验证

通过key_len判断索引使用完整性:

EXPLAIN SELECT * FROM table WHERE col1 = 1 AND col2 = 'abc';
-- 如果key_len小于预期,说明未完全使用复合索引

2. 连接顺序分析

观察多表JOIN时表的处理顺序,确保驱动表是小表。

3. 成本估算验证

比较rows和实际数据量,判断统计信息是否准确:

ANALYZE TABLE table_name; -- 更新统计信息

总结

掌握EXPLAIN是MySQL性能优化的基本功。关键要点:

  1. 重点关注type列,至少达到range级别
  2. 警惕Using filesort和Using temporary
  3. 确保实际使用了合适的索引(key列)
  4. 注意rows值过大的查询
  5. 对于复杂查询,使用EXPLAIN FORMAT=JSON获取更详细信息

通过系统性地分析EXPLAIN结果,可以精准定位查询性能瓶颈,制定针对性的优化策略。

你可能感兴趣的:(webank,数据库,sql,java)