分析SQL 语句执行慢的问题
确认慢 SQL:通过慢查询日志定位具体 SQL。排除环境干扰:检查锁等待、资源瓶颈(CPU / 内存 / IO)。分析执行计划:通过explain查看typekeyExtra等字段,定位索引问题、扫描方式问题。针对性优化:从索引、SQL 改写、表结构、参数等维度解决瓶颈。
分析 SQL 语句执行慢的问题,需要从确认现象、收集信息、分析执行计划、定位瓶颈四个核心步骤入手,结合数据库工具和执行细节逐步排查。以下是系统的分析方法(以 MySQL 为例,其他数据库思路类似,工具略有差异):
一、确认慢 SQL:先明确 “哪些 SQL 执行慢”
首先需要定位具体的慢 SQL 语句,避免盲目优化。核心手段是启用慢查询日志,记录执行时间超过阈值的 SQL。
1. 启用慢查询日志(MySQL 为例)
- 开启配置:在
my.cnf或my.ini中设置:slow_query_log = 1 # 开启慢查询日志 slow_query_log_file = /var/log/mysql/mysql-slow.log # 日志路径 long_query_time = 1 # 阈值(秒),超过此时间的SQL会被记录 log_queries_not_using_indexes = 1 # 记录未使用索引的SQL(可选,辅助分析) - 重启 MySQL 生效,或动态生效(无需重启):
set global slow_query_log = 1; set global long_query_time = 1;
2. 分析慢查询日志
用工具解析日志,提取关键信息(执行时间、扫描行数、涉及表等):
- 自带工具:
mysqldumpslow(简单统计,如按执行次数排序):mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log # 按执行时间(t)取前10条慢SQL - 第三方工具:
pt-query-digest(更详细,支持分组、统计执行频率 / 耗时):
通过日志可明确:哪些 SQL 执行慢、平均耗时多少、执行频率如何、涉及哪些表。
二、收集执行上下文:判断 “是否受环境影响”
慢 SQL 可能不是 SQL 本身的问题,而是执行时的环境干扰(如锁等待、并发冲突、资源不足),需先排除。
1. 查看实时执行状态
用show processlist查看 SQL 执行时的状态,判断是否被阻塞:
show processlist; # 查看当前所有连接的SQL执行状态
- 关键字段解读:
State:SQL 的当前状态(如Sending data表示正在传输数据,Locked表示被锁阻塞,Waiting for table metadata lock表示元数据锁等待)。Time:已执行时间(秒),若长时间不变且State异常(如Locked),可能是被阻塞。
2. 检查锁和阻塞
若State显示锁等待,需进一步分析锁来源:
- 查看表锁:
show open tables where in_use > 0; # 查看被锁定的表 - 查看行锁(InnoDB):
select * from information_schema.innodb_locks; # 当前持有/等待的行锁 select * from information_schema.innodb_lock_waits; # 锁等待关系
若存在长时锁等待,需先解决锁冲突(如优化事务时长、避免长事务)。
3. 检查资源瓶颈
SQL 执行慢可能是服务器资源不足导致(CPU、内存、IO):
- CPU:用
top查看数据库进程(如mysqld)的 CPU 占用,若过高可能是 SQL 计算密集(如大量排序、聚合)。 - 内存:用
free -m查看内存使用,若内存不足可能导致频繁磁盘交换(swap),拖慢执行。 - IO:用
iostat查看磁盘 IO 使用率(如%util接近 100%),可能是 SQL 涉及大量磁盘读写(如全表扫描)。
三、核心:分析执行计划,定位 SQL 本身的问题
排除环境干扰后,需聚焦 SQL 本身的执行逻辑 —— 通过执行计划分析 SQL 的 “执行路径”,判断是否存在低效操作(如全表扫描、索引失效)。
1. 生成执行计划
用explain或explain analyze(MySQL 8.0+)获取执行计划:
explain select * from order where user_id = 100 and create_time > '2023-01-01';
explain:输出估计的执行计划(基于统计信息)。explain analyze:输出实际执行计划(包含真实耗时、扫描行数,更准确)。
2. 解读执行计划的关键字段
执行计划的核心字段用于判断 SQL 的低效点,重点关注以下 6 个字段:
| 字段 | 含义 | 关键值解读 |
|---|---|---|
id |
执行顺序标识 | 越大越先执行;相同 id 表示同一层级(如多表连接)。 |
select_type |
查询类型 | SIMPLE(简单查询)、SUBQUERY(子查询)、DERIVED(衍生表)等,子查询可能低效。 |
table |
涉及的表 | 若显示derivedN表示衍生表(子查询生成的临时表),可能增加开销。 |
type |
访问类型(核心) | 效率从低到高:ALL(全表扫描)→index(全索引扫描)→range(范围扫描)→ref(索引匹配)→eq_ref(唯一索引匹配)→const(常量匹配)。ALL和index通常是性能瓶颈。 |
key |
实际使用的索引 | 若为NULL表示未使用索引(需检查是否有合适索引,或索引失效)。 |
rows |
估计扫描的行数 | 数值越大,执行成本越高(全表扫描时接近表总数据量)。 |
Extra |
额外信息(核心) | - Using filesort:需要额外排序(未用索引排序,效率低);- Using temporary:使用临时表(如分组未用索引,效率低);- Using where; Using index:覆盖索引(高效);- Using index condition:索引下推(高效);- Using where; Using filesort:需过滤 + 排序(低效)。 |
3. 常见执行计划问题及原因
通过执行计划的关键字段,可快速定位 SQL 的低效原因:
-
问题 1:
type = ALL(全表扫描)
原因:表无合适索引;索引失效(如where中对索引列做函数操作:where date(create_time) = '2023-01-01');索引列被隐式转换(如varchar列用int条件:where phone = 13800138000,实际phone是字符串)。 -
问题 2:
Extra出现Using filesort
原因:order by的列未建索引,或索引顺序与排序顺序不一致(如索引是(a,b),但排序用order by b,a)。 -
问题 3:
Extra出现Using temporary
原因:group by的列未建索引;distinct操作未用索引;多表连接时临时表存储中间结果。 -
问题 4:
rows数值过大
原因:统计信息过时(数据库估计的行数与实际不符,导致执行计划选错);索引粒度太粗(如低基数索引,如 “性别” 列,即使使用索引也需扫描大量行)。
四、优化措施:针对性解决瓶颈
根据分析结果,针对性优化:
1. 优化索引
-
添加缺失索引:对
where、order by、group by涉及的列创建合适索引(如联合索引需注意顺序:区分度高的列放前面)。
例:where user_id = 100 and create_time > '2023-01-01',可建联合索引(user_id, create_time)。 -
修复失效索引:
- 避免对索引列做函数操作(如
date(create_time)改为create_time between '2023-01-01 00:00:00' and '2023-01-01 23:59:59')。 - 避免隐式类型转换(如
phone = '13800138000',与列类型一致)。 - 避免前缀通配符(如
name like '%abc'无法使用索引,改为name like 'abc%')。
- 避免对索引列做函数操作(如
2. 改写 SQL
-
拆分复杂查询:子查询改连接(
join),避免衍生表(derived)。
例:select * from (select id from t1 where a=1) t where t.id in (select id from t2)改为select t1.id from t1 join t2 on t1.id = t2.id where t1.a=1。 -
优化排序 / 分组:确保
order by/group by使用索引,减少Using filesort/Using temporary。
例:order by a, b对应联合索引(a, b),且排序方向一致(如均为asc)。 -
限制返回行数:避免
select *,只查需要的列(覆盖索引场景更高效);用limit减少返回数据量。
3. 优化表和数据
-
更新统计信息:若
rows估计值与实际偏差大,需更新统计信息(MySQL:analyze table t1;;Oracle:dbms_stats.gather_table_stats('schema', 't1');)。 -
分表分库:若表数据量过大(如千万级以上),全表扫描或索引扫描成本高,可按时间、地域等维度分表(如
order_202301、order_202302)。 -
优化表结构:避免大字段(如
text)存储在主表,可拆分到子表;用char代替varchar(固定长度场景)减少碎片。
4. 调整数据库参数
- 增加
innodb_buffer_pool_size(InnoDB 缓存),减少磁盘 IO。 - 调整
sort_buffer_size(排序缓存)、join_buffer_size(连接缓存),避免因缓存不足导致磁盘临时文件。
总结:分析流程回顾
- 确认慢 SQL:通过慢查询日志定位具体 SQL。
- 排除环境干扰:检查锁等待、资源瓶颈(CPU / 内存 / IO)。
- 分析执行计划:通过
explain查看type、key、Extra等字段,定位索引问题、扫描方式问题。 - 针对性优化:从索引、SQL 改写、表结构、参数等维度解决瓶颈。
更多推荐




所有评论(0)