SQL执行计划是数据库执行SQL语句时的详细步骤说明,记录了数据读取方式、表关联顺序、索引使用情况等关键信息,是排查SQL性能问题的核心依据。掌握执行计划的分析方法和对应优化思路,能够有效提升数据查询和处理效率,减少数据库资源消耗。

一、SQL执行计划的核心获取方式
不同数据库获取执行计划的语法略有差异,以下是常见数据库的获取方式示例:
1. MySQL获取执行计划
在目标SQL语句前添加EXPLAIN关键字即可获取执行计划:
-- 查询用户表中年龄大于20的记录执行计划 EXPLAIN SELECT * FROM user WHERE age > 20;
2. PostgreSQL获取执行计划
-- 使用EXPLAIN ANALYZE可以获取实际执行统计信息 EXPLAIN ANALYZE SELECT * FROM user WHERE age > 20;
3. Oracle获取执行计划
-- 先执行EXPLAIN PLAN命令,再查询计划表 EXPLAIN PLAN FOR SELECT * FROM user WHERE age > 20; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
二、执行计划核心指标解析
分析执行计划时,需要重点关注以下核心指标:
- id:执行顺序标识,id值越大越先执行,id相同则从上到下执行
- select_type:查询类型,包括简单查询、子查询、联合查询等类型
- table:当前步骤操作的表名
- type:访问类型,性能从好到坏依次为system>const>eq_ref>ref>range>index>ALL
- possible_keys:可能使用的索引列表
- key:实际使用的索引,若为NULL则表示未使用索引
- rows:预估需要扫描的行数,数值越小性能越好
- Extra:额外信息,包含Using index、Using where、Using temporary等关键提示
三、执行计划分析步骤
按照以下顺序分析执行计划可以快速定位问题:
1. 查看访问类型type
如果type为ALL,说明进行了全表扫描,当表数据量较大时性能会非常差,需要优先考虑添加合适的索引。如果type为index,说明扫描了全部索引树,也需要评估是否有优化空间。
2. 检查索引使用情况
对比possible_keys和key字段,如果存在可能的索引但实际未使用,需要分析未使用的原因,比如索引字段参与了函数运算、索引失效、优化器判断全表扫描更快等。
3. 关注扫描行数rows
如果某个步骤的扫描行数远大于实际返回行数,说明该步骤的资源消耗较高,需要针对性优化,比如调整索引、拆分查询条件等。
4. 查看Extra额外信息
如果出现Using temporary,说明使用了临时表,通常是因为排序、分组操作无法使用索引;如果出现Using filesort,说明进行了文件排序,也需要考虑优化索引来避免排序操作。
四、基于执行计划的优化思路
1. 索引优化
根据执行计划中未使用的可能索引,结合查询条件创建合适的联合索引,注意联合索引的最左匹配原则。避免创建冗余索引,定期清理无用索引。如果索引字段存在隐式类型转换,也会导致索引失效,需要保证查询条件和字段类型一致。
-- 为user表的age和name字段创建联合索引 CREATE INDEX idx_user_age_name ON user(age, name);
2. SQL语句改写
避免 select * 查询,只查询需要的字段,减少数据传输和扫描开销。将子查询改写为关联查询,减少临时表的使用。合理使用分页查询,避免大偏移量的limit操作,可以通过记录上一页最后一条记录的主键来优化分页。
-- 优化前的大偏移量分页 SELECT * FROM user LIMIT 100000, 10; -- 优化后的分页方式 SELECT * FROM user WHERE id > 100000 LIMIT 10;
3. 表结构优化
对于数据量过大的表,可以考虑进行分库分表,减少单表数据量。将频繁查询的字段和不常查询的字段拆分到不同的表中,避免单表字段过多。对于固定值的枚举字段,可以使用 tinyint 类型代替 varchar 类型,减少存储空间提升查询效率。
4. 统计信息更新
如果执行计划中优化器选择了错误的执行路径,可能是因为表的统计信息过期,导致优化器判断失误。定期更新表的统计信息,让优化器能够生成更合理的执行计划。
-- MySQL更新表统计信息 ANALYZE TABLE user;
五、常见优化案例
假设存在如下执行计划问题:查询订单表中2024年且金额大于100的订单,执行计划中type为ALL,扫描行数为100万,未使用索引。
优化步骤:
- 首先检查订单表的create_time和amount字段是否有索引,发现无对应索引
- 创建联合索引
CREATE INDEX idx_order_create_amount ON order(create_time, amount); - 再次执行EXPLAIN,发现type变为range,扫描行数降低到1万,key显示使用了新建的索引
- 如果查询还需要返回订单详情字段,可以考虑将详情字段拆分到子表,避免回表查询开销,或者将常用字段加入联合索引实现索引覆盖
通过以上步骤,原本需要扫描全表的查询,扫描行数降低了99%,查询耗时从2秒降低到50毫秒以内,性能提升明显。