SQL 执行计划解析:从 EXPLAIN 到性能优化的完整指南

🔍 SQL 执行计划解析:从 EXPLAIN 到性能优化的完整指南

文章目录

  • [🔍 SQL 执行计划解析:从 EXPLAIN 到性能优化的完整指南](#🔍 SQL 执行计划解析:从 EXPLAIN 到性能优化的完整指南)
  • [🎯 一、为什么要理解执行计划](#🎯 一、为什么要理解执行计划)
    • [💡 SQL 性能瓶颈的常见原因](#💡 SQL 性能瓶颈的常见原因)
    • [📊 性能问题统计](#📊 性能问题统计)
  • [⚡ 二、EXPLAIN 命令详解](#⚡ 二、EXPLAIN 命令详解)
    • [🔧 基本用法与输出格式](#🔧 基本用法与输出格式)
    • [📝 执行计划解读框架](#📝 执行计划解读框架)
    • [🔍 三、关键字段深度解析](#🔍 三、关键字段深度解析)
    • [📋 select_type - 查询类型](#📋 select_type - 查询类型)
    • [🏗️ type - 访问类型(性能关键)](#🏗️ type - 访问类型(性能关键))
    • [🔑 key - 实际使用的索引](#🔑 key - 实际使用的索引)
    • [📊 rows - 预估扫描行数](#📊 rows - 预估扫描行数)
    • [💫 Extra - 额外信息(关键提示)](#💫 Extra - 额外信息(关键提示))
  • [🛠️ 四、实战案例分析](#🛠️ 四、实战案例分析)
    • [📈 案例一:单表查询优化](#📈 案例一:单表查询优化)
    • [🔗 案例二:多表 JOIN 优化](#🔗 案例二:多表 JOIN 优化)
    • [🎯 案例三:索引覆盖优化](#🎯 案例三:索引覆盖优化)
  • [💡 五、总结与优化指南](#💡 五、总结与优化指南)
    • [🎯 执行计划分析 checklist](#🎯 执行计划分析 checklist)
    • [⚡ 优化黄金法则](#⚡ 优化黄金法则)
    • [🔧 高级优化工具](#🔧 高级优化工具)
    • [📚 持续学习资源](#📚 持续学习资源)

🎯 一、为什么要理解执行计划

💡 SQL 性能瓶颈的常见原因

SQL性能瓶颈 全表扫描 索引失效 JOIN顺序不当 临时表使用 排序文件外

执行计划的价值​​:

  • ​​提前发现问题​​:在SQL执行前预测性能

  • ​​​​优化依据​​:提供具体的优化方向

​​- ​​学习工具​​:理解数据库工作原理

📊 性能问题统计

根据对生产环境SQL问题的分析

  • 65% 的慢查询源于索引使用不当

​​- ​​20%​​ 由于JOIN操作效率低下

​​- ​​10%​​ 因为数据量过大缺乏分页

​​- ​​5%​​ 其他原因(锁竞争、配置问题等)

⚡ 二、EXPLAIN 命令详解

🔧 基本用法与输出格式

​​EXPLAIN 基本命令​​

sql 复制代码
-- 基本用法
EXPLAIN SELECT * FROM users WHERE age > 25;

-- 详细格式(MySQL 8.0+)
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE age > 25;

-- 实际执行计划(MySQL 8.0.18+)
EXPLAIN ANALYZE SELECT * FROM users WHERE age > 25;

​​典型EXPLAIN输出​​

sql 复制代码
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | users | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1000 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+

📝 执行计划解读框架

EXPLAIN输出 查询类型分析 访问方法分析 索引使用分析 额外信息分析 select_type type key, rows Extra

🔍 三、关键字段深度解析

🆔 id - 查询标识符
​​id 字段的含义​​:

  • ​​相同id​​:同一查询层级

  • ​​递增id​​:子查询或UNION操作

  • ​​NULL id​​:派生表或临时结果

​​示例分析​​

sql 复制代码
-- 复杂查询示例
EXPLAIN 
SELECT u.name, 
       (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) as order_count
FROM users u
WHERE u.status = 'active';

​​输出解读​​

sql 复制代码
+----+--------------------+-------+------------+------+---------------+---------+---------+------+------+----------+--------------------------+
| id | select_type        | table | partitions | type | possible_keys | key     | key_len | ref  | rows | filtered | Extra                    |
+----+--------------------+-------+------------+------+---------------+---------+---------+------+------+----------+--------------------------+
|  1 | PRIMARY            | u     | NULL       | ref  | idx_status    | idx_status | 102    | const| 500  |   100.00 | Using where              |
|  2 | DEPENDENT SUBQUERY | o     | NULL       | ref  | idx_user_id   | idx_user_id | 5      | func | 10   |   100.00 | Using index              |
+----+--------------------+-------+------------+------+---------------+---------+---------+------+------+----------+--------------------------+

📋 select_type - 查询类型

​​常见 select_type 值​​

类型 描述 出现场景
SIMPLE 简单查询 不含子查询或UNION
PRIMARY 最外层查询 复杂查询的主查询
SUBQUERY 子查询 WHERE子句中的子查询
DERIVED 派生表 FROM子句中的子查询
UNION UNION查询 UNION第二个及以后的查询
UNION RESULT UNION结果 UNION结果的临时表

🏗️ type - 访问类型(性能关键)

​​type 性能等级​​(从优到劣):
type性能等级 system const eq_ref ref range index ALL

​​详细解释​​

sql 复制代码
-- 最佳情况:const(主键或唯一索引等值查询)
EXPLAIN SELECT * FROM users WHERE id = 1;
-- type: const

-- 良好情况:ref(非唯一索引等值查询)
EXPLAIN SELECT * FROM users WHERE email = 'user@example.com';
-- type: ref

-- 最差情况:ALL(全表扫描)
EXPLAIN SELECT * FROM users WHERE name LIKE '%john%';
-- type: ALL

🔑 key - 实际使用的索引

​​索引使用分析​​:

sql 复制代码
-- 创建测试索引
CREATE INDEX idx_age ON users(age);
CREATE INDEX idx_city ON users(city);

-- 查看索引选择
EXPLAIN SELECT * FROM users WHERE age > 25 AND city = 'Beijing';

​​可能输出​​:

sql 复制代码
+----+-------------+-------+------------+------+-------------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys     | key     | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+------+-------------------+---------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | users | NULL       | ref  | idx_age,idx_city  | idx_city| 102     | const | 100  |    50.00 | Using where |
+----+-------------+-------+------------+------+-------------------+---------+---------+-------+------+----------+-------------+

📊 rows - 预估扫描行数

​​rows 字段的重要性​​

  • 数值越小越好​​:表示需要处理的数据量少

  • ​​基于统计信息​​:可能不准确,需要定期分析表

  • ​​优化参考​​:帮助识别全表扫描

sql 复制代码
-- 优化前:全表扫描
EXPLAIN SELECT * FROM users WHERE name LIKE '%john%';
-- rows: 10000(扫描所有行)

-- 优化后:索引扫描
EXPLAIN SELECT * FROM users WHERE email = 'john@example.com';
-- rows: 1(只扫描1行)

💫 Extra - 额外信息(关键提示)

​​重要 Extra 值解读​​

Extra 值 含义 优化建议
Using where 使用WHERE过滤 正常,无需优化
Using index 覆盖索引 优秀,无需回表
Using temporary 使用临时表 考虑优化查询或索引
Using filesort 文件排序 添加合适的索引
Range checked for each record 索引选择困难 优化查询条件

🛠️ 四、实战案例分析

📈 案例一:单表查询优化

​​问题查询​​:

sql 复制代码
SELECT * FROM products 
WHERE category = 'electronics' 
AND price BETWEEN 1000 AND 2000
ORDER BY created_at DESC;

​​初始执行计划​​:

sql 复制代码
EXPLAIN SELECT * FROM products 
WHERE category = 'electronics' 
AND price BETWEEN 1000 AND 2000
ORDER BY created_at DESC;

​​输出分析​​

sql 复制代码
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                       |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
|  1 | SIMPLE      | products | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 5000 |     5.00 | Using where; Using filesort |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+

问题诊断​​

  • type: ALL:全表扫描

  • Using filesort:文件排序,性能差

  • 缺少合适索引

​​优化方案​​

sql 复制代码
-- 创建复合索引
CREATE INDEX idx_category_price_created 
ON products(category, price, created_at);

-- 再次查看执行计划
EXPLAIN SELECT * FROM products 
WHERE category = 'electronics' 
AND price BETWEEN 1000 AND 2000
ORDER BY created_at DESC;

​​优化后输出​​

sql 复制代码
+----+-------------+----------+------------+-------+---------------------------+---------------------------+---------+------+------+----------+-------+
| id | select_type | table    | partitions | type  | possible_keys             | key                       | key_len | ref  | rows | filtered | Extra |
+----+-------------+----------+------------+-------+---------------------------+---------------------------+---------+------+------+----------+-------+
|  1 | SIMPLE      | products | NULL       | range | idx_category_price_created| idx_category_price_created| 108     | NULL | 50   |   100.00 | Using index condition     |
+----+-------------+----------+------------+-------+---------------------------+---------------------------+---------+------+------+----------+-------+

🔗 案例二:多表 JOIN 优化

​​问题查询​​​​:

sql 复制代码
SELECT u.name, o.order_date, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.city = 'Beijing'
AND o.status = 'completed'
ORDER BY o.order_date DESC;

​​​​初始执行计划​​​​:

sql 复制代码
EXPLAIN SELECT u.name, o.order_date, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.city = 'Beijing'
AND o.status = 'completed'
ORDER BY o.order_date DESC;

​​​​输出分析​​​​:

sql 复制代码
+----+-------------+-------+------------+--------+---------------+---------+---------+-------------------+------+----------+---------------------------------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref               | rows | filtered | Extra                           |
+----+-------------+-------+------------+--------+---------------+---------+---------+-------------------+------+----------+---------------------------------+
|  1 | SIMPLE      | u     | NULL       | ALL    | PRIMARY       | NULL    | NULL    | NULL              | 1000 |    10.00 | Using where; Using filesort     |
|  1 | SIMPLE      | o     | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | test.u.id         | 1    |    10.00 | Using where                     |
+----+-------------+-------+------------+--------+---------------+---------+---------+-------------------+------+----------+---------------------------------+

问题诊断​​:

  • u表全表扫描

  • 连接顺序可能不是最优

  • 缺少合适的索引

​​优化方案​​

sql 复制代码
-- 为users表添加城市索引
CREATE INDEX idx_city ON users(city);

-- 为orders表添加状态索引
CREATE INDEX idx_status_user ON orders(status, user_id);

-- 使用STRAIGHT_JOIN强制连接顺序
EXPLAIN SELECT STRAIGHT_JOIN u.name, o.order_date, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.city = 'Beijing'
AND o.status = 'completed'
ORDER BY o.order_date DESC;

​​优化后输出​​:

sql 复制代码
+----+-------------+-------+------------+------+---------------------+---------------------+---------+--------------------+------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys       | key                 | key_len | ref                | rows | filtered | Extra                           |
+----+-------------+-------+------------+------+---------------------+---------------------+---------+--------------------+------+----------+---------------------------------+
|  1 | SIMPLE      | u     | NULL       | ref  | PRIMARY,idx_city    | idx_city            | 102     | const              | 100  |   100.00 | Using temporary; Using filesort |
|  1 | SIMPLE      | o     | NULL       | ref  | idx_status_user     | idx_status_user     | 104     | test.u.id,const    | 5    |   100.00 | NULL                            |
+----+-------------+-------+------------+------+---------------------+---------------------+---------+--------------------+------+----------+---------------------------------+

🎯 案例三:索引覆盖优化

​​问题查询​​​​:

sql 复制代码
SELECT COUNT(*) FROM user_actions 
WHERE user_id = 1001 AND action_date > '2023-01-01';

​​​​初始情况​​​​:

sql 复制代码
-- 现有索引:INDEX (user_id)
EXPLAIN SELECT COUNT(*) FROM user_actions 
WHERE user_id = 1001 AND action_date > '2023-01-01';

​​​​输出分析​​​​:

sql 复制代码
+----+-------------+-------------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table       | partitions | type | possible_keys | key     | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | user_actions| NULL       | ref  | idx_user_id   | idx_user_id | 5      | const | 500  |    33.33 | Using where |
+----+-------------+-------------+------------+------+---------------+---------+---------+-------+------+----------+-------------+

优化方案​​:

sql 复制代码
-- 创建覆盖索引
CREATE INDEX idx_user_id_action_date 
ON user_actions(user_id, action_date);

-- 再次执行
EXPLAIN SELECT COUNT(*) FROM user_actions 
WHERE user_id = 1001 AND action_date > '2023-01-01';

​​优化后输出​​:

sql 复制代码
+----+-------------+-------------+------------+-------+---------------------------+---------------------------+---------+------+------+----------+--------------------------+
| id | select_type | table       | partitions | type  | possible_keys             | key                       | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------------+------------+-------+---------------------------+---------------------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | user_actions| NULL       | range | idx_user_id_action_date   | idx_user_id_action_date   | 8       | NULL | 100  |   100.00 | Using where; Using index |
+----+-------------+-------------+------------+-------+---------------------------+---------------------------+---------+------+------+----------+--------------------------+

优化效果​​

  • Using index:实现索引覆盖,无需回表

  • 查询性能提升 3-5 倍

💡 五、总结与优化指南

🎯 执行计划分析 checklist

​​快速诊断流程​​:
是 否 是 否 是 否 查看执行计划 type = ALL? 全表扫描警告 Extra有filesort? 添加合适索引 优化排序字段索引 rows值过大? 优化查询条件 查询已优化

⚡ 优化黄金法则

​​索引优化策略​​

  • 最左前缀原则​​:复合索引的顺序很重要

  • ​​覆盖索引​​:尽可能让索引包含查询字段

  • ​​索引选择性​​:选择高区分度的列建索引

​​查询优化技巧​​:

sql 复制代码
-- 避免 SELECT *
SELECT id, name, email FROM users; -- 只选择需要的字段

-- 优化 WHERE 条件
SELECT * FROM products WHERE price > 1000; -- 使用范围查询
SELECT * FROM products WHERE id IN (1,2,3); -- 使用IN而不是OR

-- 合理使用 LIMIT
SELECT * FROM large_table LIMIT 1000; -- 限制返回行数

🔧 高级优化工具

​​MySQL 8.0 新特性​​

sql 复制代码
-- 执行计划可视化
EXPLAIN FORMAT=TREE SELECT * FROM users WHERE age > 25;

-- 实际执行分析
EXPLAIN ANALYZE SELECT * FROM users WHERE age > 25;

-- 索引建议
SELECT * FROM sys.schema_index_statistics;

​​性能监控脚本​​:

sql 复制代码
-- 查找全表扫描的查询
SELECT * FROM sys.statements_with_full_table_scans;

-- 查找需要索引的查询
SELECT * FROM sys.statements_with_errors_or_warnings;

📚 持续学习资源

​​推荐分析命令​​

sql 复制代码
-- 定期分析表统计信息
ANALYZE TABLE users;

-- 检查索引使用情况
SHOW INDEX FROM users;

-- 监控慢查询
SHOW VARIABLES LIKE 'slow_query_log%';
相关推荐
qqxhb2 小时前
系统架构设计师备考第30天——关系数据库
数据库·系统架构·数据库设计·关系模式·范式·关系远算·完整性规则
YAY_tyy2 小时前
【JavaScript 性能优化实战】第五篇:运行时性能优化进阶(懒加载 + 预加载 + 资源优先级)
前端·javascript·性能优化
微笑尅乐2 小时前
神奇的位运算——力扣136.只出现一次的数字
java·算法·leetcode·职场和发展
小心草里有鬼2 小时前
Linux 数据库 Mysql8 主从复制
linux·运维·数据库·sql·mysql
Chan162 小时前
【 设计模式 | 结构型模式 代理模式 】
java·spring boot·后端·设计模式·intellij-idea
柯南二号2 小时前
【AI】【Java后端】RAG 实战示例:SpringBoot + 向量检索 + LLM 问答系统
java·人工智能·spring boot
chen_note2 小时前
Keepalived两个集群实验
linux·服务器·数据库·keepalived·高可用集群
Mr.Pascal2 小时前
后端直接返回错误信息的Map 和 抛出异常(异常机制)优劣势对比
java·springboot
zcychong3 小时前
如何让A、B、C三个线程按严格顺序执行(附十一种解)?
java·面试