用心打造
VPS知识分享网站

MySQL查询突然变慢怎么排查?慢SQL定位教程

本文于 2026-08-11 15:35 更新,部分内容具有时效性,如有失效,请留言

同一条查询昨天只需要几十毫秒,今天却突然跑到几秒,最容易得到的答案是索引失效了。但查询变慢也可能来自锁等待、数据量变化、执行计划改变、磁盘延迟、缓存命中下降,甚至应用一次取回了远超平时的数据。

排查慢SQL的难点不是找到一条耗时查询,而是证明时间到底花在哪一层。直接添加索引、重启MySQL或清空缓存,可能让现象暂时变化,却会破坏现场,也可能引入额外写入成本。

下面以MySQL 8.0与8.4的常见InnoDB环境为例。MariaDB、MySQL 5.7和云数据库的Performance Schema字段、权限与日志入口可能不同。生产库执行任何修改语句前,都应先确认备份、主从关系、业务低峰与回滚方案。

查询突然变慢

先固定慢下来的时间和业务入口

先记录故障时间、时区、接口、租户或用户条件,以及正常与异常耗时。应用日志里最好能找到请求ID和SQL执行时间。只有页面慢,却没有SQL时间,不能直接认定数据库就是瓶颈。

在数据库服务器记录当前时间与版本:

SELECT NOW(6), @@hostname, @@version, @@version_comment;

把结果与应用、代理和监控的时区对齐。接着确认影响范围:是一条SQL、一个接口、某个表,还是全部查询都变慢。全部请求同时变慢,要优先检查数据库资源、连接数和存储;单一查询变慢,才更适合从语句与执行计划入手。

还要保留原始SQL结构和参数特征。手机号、订单号、令牌等敏感值要脱敏,但不能把条件全部删掉,因为不同参数可能选择完全不同的数据范围和执行计划。

查看正在执行的SQL与等待状态

故障仍在发生时,先读取当前连接:

SHOW FULL PROCESSLIST;

重点看Time、State、Info、连接来源和数据库。Sending data不只表示向客户端发送数据,也可能覆盖读取与处理行的阶段;Waiting for table metadata lock、等待行锁、临时表或排序状态则给出更具体方向。

MySQL 8.0与8.4可以从Performance Schema读取更完整的当前语句:

SELECT
  t.PROCESSLIST_ID,
  t.PROCESSLIST_USER,
  t.PROCESSLIST_HOST,
  t.PROCESSLIST_TIME,
  t.PROCESSLIST_STATE,
  es.EVENT_NAME,
  es.SQL_TEXT
FROM performance_schema.threads AS t
LEFT JOIN performance_schema.events_statements_current AS es
  ON es.THREAD_ID = t.THREAD_ID
WHERE t.TYPE = 'FOREGROUND'
ORDER BY t.PROCESSLIST_TIME DESC;

没有结果时先确认Performance Schema是否启用,以及对应consumer和instrument是否采集语句。不要为了追一次故障在高峰期盲目开启全部明细事件,额外采集也有资源成本。

看到长时间运行的连接不要立刻KILL。先确认它是只读查询、写事务、DDL还是备份任务,并评估终止后的回滚时间和复制影响。读取现场是第一步,终止会话属于有业务风险的操作。

用语句摘要找出最耗时的SQL类型

瞬时查询可能在你登录数据库前已经结束。Performance Schema的语句摘要会按规范化SQL聚合统计,适合寻找总耗时高、平均耗时高或扫描行数异常的语句:

SELECT
  SCHEMA_NAME,
  DIGEST_TEXT,
  COUNT_STAR,
  ROUND(SUM_TIMER_WAIT / 1000000000000, 2) AS total_seconds,
  ROUND(AVG_TIMER_WAIT / 1000000000000, 6) AS avg_seconds,
  SUM_ROWS_EXAMINED,
  SUM_ROWS_SENT,
  SUM_NO_INDEX_USED,
  FIRST_SEEN,
  LAST_SEEN
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME IS NOT NULL
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;

总耗时高可能是单次非常慢,也可能是执行次数极多;平均耗时高更偏向单次延迟;扫描行数与返回行数差距很大,说明为了返回少量结果扫描了很多行,但它仍不是必须加索引的唯一依据。

MySQL 8.4的摘要还可以提供分位延迟和查询样本,具体列要按实际版本确认。摘要从Performance Schema开始采集或上次清空后累计,因此要结合FIRST_SEEN、LAST_SEEN和监控时间窗口。直接清空摘要表会破坏历史证据,除非已经导出基线并明确需要重新计数。

慢查询日志应该怎样临时使用

摘要只能提供聚合视角,想保留每次超过阈值的执行,可以核对慢查询日志配置:

SHOW VARIABLES WHERE Variable_name IN (
  'slow_query_log',
  'slow_query_log_file',
  'long_query_time',
  'log_output',
  'min_examined_row_limit'
);

没有开启时,可以在评估磁盘容量、日志权限和写入开销后临时启用:

SET GLOBAL long_query_time = 1;
SET GLOBAL slow_query_log = 'ON';

全局long_query_time对新建立的会话生效,已有应用连接可能仍使用旧的会话值。云数据库也可能只允许在参数组或控制台设置。开启前记录原值,排查完成后恢复,并给日志设置轮转和容量告警。

阈值不能一味设得很低。高并发数据库把所有几十毫秒查询都写入文件,日志本身会快速增长。先用1秒或符合业务SLO的阈值捕获,再根据结果缩小范围。日志中的完整SQL可能含敏感参数,访问权限和保留周期必须受控。

对具体SQL读取执行计划

拿到代表性SQL后,先在同一个库、相近参数和相近数据分布下执行普通EXPLAIN:

EXPLAIN FORMAT=TREE
SELECT ...;

关注访问顺序、使用的索引、估算行数、连接方式、排序和临时表。传统表格格式中的type为ALL表示全表扫描,但小表全扫可能比走索引更合理。Using filesort也不等于一定错误,要结合实际行数和排序成本判断。

MySQL 8.0.18之后支持EXPLAIN ANALYZE,它会真正执行语句,并返回估算值与实际迭代器耗时:

EXPLAIN ANALYZE
SELECT ...;

因为它会执行SQL,生产环境不能随便对大查询、写语句或不可控函数使用。先用只读副本或脱敏测试环境验证,设置客户端超时,并确认不会锁住大表。对于正在运行且可解释的连接,具备相应权限时也可以评估EXPLAIN FOR CONNECTION,避免重新执行同一条重查询。

执行计划中估算一百行、实际却扫描百万行,说明统计信息或数据分布可能影响了优化器判断;大部分时间耗在某个嵌套循环、排序或回表阶段,则能进一步确定索引或SQL改写方向。

检查索引、数据量和统计信息变化

查看表定义与索引:

SHOW CREATE TABLE db_name.table_name\G
SHOW INDEX FROM db_name.table_name;

核对WHERE、JOIN、ORDER BY和GROUP BY涉及的列,以及联合索引的列顺序。不是每个条件都单独建一个索引。联合索引能否使用与最左前缀、范围条件、排序方向和数据选择性有关。

查询突然变慢还要对比近期数据增长、批量导入、表结构变更和索引可见性。索引仍存在,不代表统计信息仍能准确反映当前分布。MySQL官方文档说明,ANALYZE TABLE会更新键分布统计,优化器可据此选择计划。

但ANALYZE TABLE不是无风险按钮。不同存储引擎的锁行为不同,InnoDB分析期间也可能产生短时读锁和I/O。先在副本或测试环境评估,安排低峰执行,并记录执行前后计划。统计更新后计划也可能变差,因此要保留回滚与观察方案。

区分锁等待与纯执行慢

查询耗时长,不一定是扫描慢。检查InnoDB事务和锁等待:

SELECT * FROM information_schema.innodb_trx\G
SELECT * FROM performance_schema.data_lock_waits\G

等待者耗时很长,而实际执行时间很短,优化索引可能不是第一步。需要找到阻塞事务、事务开始时间、最后执行SQL和业务来源,再决定等待、提交、回滚或终止。

元数据锁还会让普通查询排在DDL后面。遇到表结构变更期间突然变慢,应同时检查performance_schema.metadata_locks。不能只看PROCESSLIST里当前显示的SQL,因为真正的阻塞者可能处于Sleep状态但事务没有提交。

终止阻塞连接前评估回滚量。大事务被KILL后,回滚可能继续占用I/O很长时间。最小改动是先停止产生新请求,联系事务所属应用,再在确认业务影响后处理阻塞者。

检查数据库服务器资源与存储

多条无关SQL同时变慢,通常要把视角拉回服务器。记录CPU、内存、交换分区、磁盘延迟和网络:

uptime
free -h
vmstat 1 10
iostat -xz 1 10

vmstat中持续的si、so表示交换活动;iostat要关注设备延迟、队列和利用率,不能只看吞吐。虚拟机里的CPU steal持续偏高,可能说明宿主机资源争用;磁盘延迟突然升高,则会让所有需要读页或刷盘的查询一起变慢。

再查看InnoDB缓冲池和临时表相关状态,结合命中率、脏页、检查点压力与磁盘读写判断。单个瞬时值不够,应与正常时段基线比较。服务器重启后缓存变冷,查询短时间变慢也可能是预期现象。

现有机器无法区分存储与SQL问题时,可以在 萤光云 创建相同MySQL版本的脱敏数据副本,或使用按小时计费的 LightNode 做执行计划和存储对照。测试库必须移除真实账号与敏感数据,也不能把生产备份直接暴露到公网。

按证据选择最小修复

缺少合适索引时,先在测试环境创建并比较执行计划、查询耗时和写入成本。统计信息过旧时,评估后更新统计;返回数据过多时,优化分页与字段选择;锁等待时,缩短事务并统一访问顺序;资源饱和时,再考虑限流、读写拆分或扩容。

不要一次添加多个重叠索引。索引会占磁盘、增加缓存压力,并拖慢INSERT、UPDATE和DELETE。SQL改写也要确认结果完全一致,尤其是NULL、排序、时区和字符集条件。

需要临时止损时,可以限制问题接口、降低并发或把报表任务移到副本。直接重启MySQL会终止连接并清空部分现场,对大库还可能造成较长恢复时间,不能作为常规慢查询修复方法。

用同一参数和业务高峰验收

修复后用原SQL、相同参数范围和接近的数据量复测,比较执行计划、实际扫描行数、平均延迟与95分位延迟。只测试一个刚好命中缓存的参数,结论可能过于乐观。

继续观察一个完整业务高峰,确认慢查询数量、CPU、磁盘延迟和锁等待同步改善,写入延迟没有因新索引明显上升。主从环境还要查看复制延迟与从库计划,不能只验收主库。

最终记录应包含故障时间、SQL摘要、原执行计划、直接证据、实施的最小改动和修改后指标。能够复述为什么慢、为什么这样改、怎样证明恢复,才算真正关闭问题。

常见问题

查询突然变慢,是不是先执行ANALYZE TABLE?

不建议直接执行。先对比执行计划、估算行数、数据变化和统计信息,再评估锁与I/O影响。没有证据时更新统计,可能让计划继续变化。

EXPLAIN很快,为什么真实查询仍然慢?

普通EXPLAIN主要展示优化器计划,不会完整执行查询。真实耗时还受缓存、锁、磁盘、返回数据量和客户端读取影响,需要结合运行时等待与受控的EXPLAIN ANALYZE。

给慢SQL加索引以后就结束了吗?

还需要验证不同参数、业务高峰和写入成本。索引可能只改善某一类条件,也可能增加更新开销或与已有索引重复。

赞(0)
未经允许不得转载;国外VPS测评网 » MySQL查询突然变慢怎么排查?慢SQL定位教程
分享到