分析 SQL 语句执行慢的问题,需要从确认现象、收集信息、分析执行计划、定位瓶颈四个核心步骤入手,结合数据库工具和执行细节逐步排查。以下是系统的分析方法(以 MySQL 为例,其他数据库思路类似,工具略有差异):

一、确认慢 SQL:先明确 “哪些 SQL 执行慢”

首先需要定位具体的慢 SQL 语句,避免盲目优化。核心手段是启用慢查询日志,记录执行时间超过阈值的 SQL。

1. 启用慢查询日志(MySQL 为例)
  • 开启配置:在my.cnfmy.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. 生成执行计划

explainexplain 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(常量匹配)。ALLindex通常是性能瓶颈。
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. 优化索引
  • 添加缺失索引:对whereorder bygroup 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_202301order_202302)。

  • 优化表结构:避免大字段(如text)存储在主表,可拆分到子表;用char代替varchar(固定长度场景)减少碎片。

4. 调整数据库参数
  • 增加innodb_buffer_pool_size(InnoDB 缓存),减少磁盘 IO。
  • 调整sort_buffer_size(排序缓存)、join_buffer_size(连接缓存),避免因缓存不足导致磁盘临时文件。

总结:分析流程回顾

  1. 确认慢 SQL:通过慢查询日志定位具体 SQL。
  2. 排除环境干扰:检查锁等待、资源瓶颈(CPU / 内存 / IO)。
  3. 分析执行计划:通过explain查看typekeyExtra等字段,定位索引问题、扫描方式问题。
  4. 针对性优化:从索引、SQL 改写、表结构、参数等维度解决瓶颈。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐