1. 执行计划解析基础
MySQL执行计划是数据库查询优化的核心工具,它揭示了数据库引擎如何处理SQL语句的内部机制。通过EXPLAIN命令,我们可以获取查询的执行路径、表访问顺序、索引使用情况等关键信息。对于中高级开发者而言,掌握执行计划解读能力,相当于获得了数据库性能调优的X光机。
执行计划输出的常见列包括:
- id:查询标识符,相同id表示同一执行单元
- select_type:查询类型(SIMPLE/PRIMARY/SUBQUERY等)
- table:访问的表名
- partitions:匹配的分区
- type:访问类型(从优到差:system > const > eq_ref > ref > range > index > ALL)
- possible_keys:可能使用的索引
- key:实际使用的索引
- key_len:使用的索引长度
- ref:列与索引的比较
- rows:预估需要检查的行数
- filtered:条件过滤后的行百分比
- Extra:额外信息(Using index/Using temporary等)
2. 关键指标深度解读
2.1 访问类型(type)解析
type列是执行计划中最重要的指标之一,它显示了MySQL如何查找表中的行。以下是常见类型的性能对比:
| 类型 | 扫描方式 | 性能 | 触发场景 |
|---|---|---|---|
| system | 系统表单行 | 最优 | 系统表且只有一行 |
| const | 主键/唯一索引等值 | 极优 | WHERE条件使用主键或唯一索引 |
| eq_ref | 关联查询唯一索引 | 优 | JOIN使用主键或唯一索引关联 |
| ref | 非唯一索引等值 | 良 | 普通索引等值查询 |
| range | 索引范围扫描 | 中 | BETWEEN/IN/>/<等范围查询 |
| index | 全索引扫描 | 较差 | 扫描整个索引树 |
| ALL | 全表扫描 | 最差 | 无可用索引 |
实战建议:生产环境应尽量避免出现index和ALL类型,通常需要通过添加合适索引来优化。
2.2 额外信息(Extra)详解
Extra列包含MySQL解决查询的额外信息,常见值及其含义:
- Using index:覆盖索引扫描,无需回表
- Using where:存储引擎检索后,服务器再过滤
- Using temporary:需要创建临时表
- Using filesort:需要额外排序操作
- Using join buffer:使用连接缓冲
- Impossible WHERE:WHERE条件永远为false
- Select tables optimized away:通过索引优化掉表访问
避坑指南:出现Using temporary和Using filesort通常需要警惕,特别是对大表操作时可能引发性能问题。
3. 执行计划实战分析
3.1 索引优化案例
假设有用户表users(id主键, name, age, create_time),分析以下查询:
sql复制EXPLAIN SELECT * FROM users WHERE age > 20 ORDER BY create_time DESC LIMIT 10;
典型问题表现:
- type: ALL(全表扫描)
- Extra: Using where; Using filesort(未使用索引且需要排序)
优化方案:
- 创建复合索引:ALTER TABLE users ADD INDEX idx_age_ctime(age, create_time);
- 改写查询确保索引使用:
sql复制EXPLAIN SELECT * FROM users
WHERE age > 20
ORDER BY age DESC, create_time DESC -- 与索引顺序一致
LIMIT 10;
优化后效果:
- type: range(范围扫描)
- Extra: Using index condition
- 不再出现filesort
3.2 连接查询优化
分析订单关联查询:
sql复制EXPLAIN SELECT o.*, u.name
FROM orders o JOIN users u ON o.user_id = u.id
WHERE o.status = 1 AND u.age > 18;
常见问题:
- 驱动表选择不当
- 连接字段无索引
- 过滤条件未下推
优化步骤:
- 确保连接字段有索引:
- ALTER TABLE orders ADD INDEX idx_user(user_id);
- ALTER TABLE users ADD INDEX idx_id_age(id, age);
- 使用STRAIGHT_JOIN强制连接顺序(谨慎使用)
- 检查执行计划确认优化效果
4. 高级调优技巧
4.1 索引选择性计算
索引选择性是衡量索引效率的重要指标:
sql复制-- 计算age字段的选择性
SELECT
COUNT(DISTINCT age) / COUNT(*) AS selectivity
FROM users;
选择性经验值:
-
0.2:适合建单列索引
- <0.1:考虑与其他列组合建复合索引
- 接近1:理想索引列
4.2 执行计划限制与扩展
MySQL 8.0+提供了更多分析工具:
- EXPLAIN ANALYZE:实际执行查询并显示耗时
- EXPLAIN FORMAT=JSON:获取更详细的JSON格式信息
- EXPLAIN FOR CONNECTION:分析正在运行的查询
示例使用:
sql复制EXPLAIN ANALYZE SELECT * FROM users WHERE age > 25;
输出包含实际执行时间、循环次数等真实运行时数据。
5. 生产环境实战经验
5.1 执行计划不稳定问题
当发现同一查询执行计划突然变化时,可能原因:
- 统计信息过时:ANALYZE TABLE更新统计信息
- 索引失效:检查索引状态SHOW INDEX FROM table
- 优化器成本估算变化:MySQL 8.0+可使用优化器提示
解决方案:
sql复制-- 强制使用指定索引
SELECT * FROM users FORCE INDEX(idx_age) WHERE age > 20;
-- 更新统计信息
ANALYZE TABLE users;
5.2 分页查询优化
典型的分页性能问题:
sql复制EXPLAIN SELECT * FROM users ORDER BY id LIMIT 100000, 10;
优化方案:
- 延迟关联:
sql复制SELECT u.* FROM users u
JOIN (SELECT id FROM users ORDER BY id LIMIT 100000, 10) t
ON u.id = t.id;
- 使用书签记录上次查询位置
优化后执行计划显示:
- 子查询使用覆盖索引
- 外层查询通过主键精确查找
6. 可视化分析工具
除了命令行解释,推荐使用:
- MySQL Workbench可视化执行计划
- Percona PMM监控查询性能
- pt-index-usage分析索引使用情况
Workbench使用示例:
- 输入查询语句
- 点击"Execution Plan"标签
- 查看图形化执行流程
- 分析各节点成本占比
7. 执行计划与索引策略
7.1 复合索引设计原则
- 最左前缀原则:INDEX(a,b,c) 只能用于a|a,b|a,b,c查询
- 等值查询列优先:WHERE a=1 AND b>2 → INDEX(a,b)
- 排序字段放在最后:ORDER BY c → INDEX(a,b,c)
- 避免冗余索引:INDEX(a), INDEX(a,b) 后者可替代前者
7.2 索引失效场景
即使有索引也可能不生效的情况:
- 隐式类型转换:WHERE varchar_col = 123
- 使用函数操作:WHERE DATE(create_time) = '2023-01-01'
- 前导模糊查询:WHERE name LIKE '%张'
- 不符合最左前缀:INDEX(a,b,c)但WHERE b=1
- OR条件未全覆盖:WHERE a=1 OR b=2(需a、b都有索引)
8. 执行计划进阶分析
8.1 优化器成本计算
MySQL基于成本模型选择执行计划,主要考虑:
- IO成本:从磁盘读取数据页的代价
- CPU成本:处理WHERE条件的计算代价
- 内存成本:排序、分组等内存操作
- 随机IO与顺序IO代价差异
查看成本估算:
sql复制EXPLAIN FORMAT=JSON SELECT * FROM users WHERE age > 20;
8.2 直方图统计信息
MySQL 8.0+支持列直方图统计:
sql复制-- 创建直方图
ANALYZE TABLE users UPDATE HISTOGRAM ON age WITH 10 BUCKETS;
-- 查看直方图
SELECT * FROM INFORMATION_SCHEMA.COLUMN_STATISTICS;
直方图帮助优化器更好地估算范围查询的选择性。
9. 执行计划与SQL改写
9.1 IN vs EXISTS优化
分析两种写法的执行计划差异:
sql复制-- IN写法
EXPLAIN SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders WHERE amount > 100);
-- EXISTS写法
EXPLAIN SELECT * FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.amount > 100);
优化建议:
- 外表大用EXISTS
- 子查询结果集小用IN
- MySQL 8.0+对两者优化程度相近
9.2 派生表合并优化
查看派生表合并情况:
sql复制-- 设置优化器开关
SET optimizer_switch = 'derived_merge=on';
EXPLAIN SELECT * FROM
(SELECT * FROM users WHERE age > 20) t
WHERE t.name LIKE '张%';
Extra中出现"Using temporary"表示未合并派生表,可尝试:
- 调整optimizer_switch
- 使用/*+ NO_MERGE() */提示
- 直接写为普通WHERE条件
10. 分布式环境执行计划
10.1 分库分表执行计划
在ShardingSphere等中间件中,执行计划分析要点:
- 实际SQL路由情况
- 归并操作类型(内存/流式)
- 分布式JOIN策略
10.2 主从复制延迟影响
执行计划显示使用索引但实际性能差,可能原因:
- 从库统计信息不同步
- 复制延迟导致临时表创建
- 从库负载高导致执行计划退化
解决方案:
- 检查Seconds_Behind_Master
- 在从库执行ANALYZE TABLE
- 使用/*+ SLAVE */提示控制路由