• 慢查询排查实战——从"系统变慢"到定位根因的完整方法论
  • 2026-07-21 06:40:11
  • 十五年数据库相关经验,做过 DBA、架构师、技术顾问。不求"颠覆",只求"靠谱"。

    翻过很多文档,也踩过很多坑。做 DBA 这些年,凌晨被叫醒的次数里,有三分之一是因为"系统变慢了"。

    "慢"这个描述最折磨人。它不像"宕机"那样明确——系统还在跑,只是比平时慢了。慢了多久?谁慢了?为什么慢?一问三不知。

    这篇文章我把慢查询排查的方法论整理出来,从"听到业务说慢"到"定位到具体 SQL 和根因",每一步都说清楚。

    一、先确认:是真的慢,还是"感觉慢"接到"系统变慢了"的反馈,第一步不是去查数据库,是先确认到底是不是数据库的问题。

    怎么确认?

    看三个指标:

    响应时间。用监控工具(或者直接在应用层打日志)看接口响应时间。如果响应时间从 200ms 涨到 2s,那确实是慢了。如果只是从 200ms 涨到 300ms,用户"感觉慢",但实际没有本质问题。数据库负载。看 CPU、内存、磁盘 I/O、连接数。如果这些指标正常,慢大概率不是数据库的问题——可能是网络、应用层逻辑、或者前端渲染。影响范围。是所有接口都慢,还是特定接口慢?如果是特定接口慢,问题范围就缩小了。我的习惯:接到慢的反馈,我会先问三个问题——"什么时候开始慢的?""是所有操作都慢还是某个操作慢?""大概慢了多少?"这三个问题能帮我快速缩小排查范围。

    二、找到慢的 SQL——三步定位法确认了是数据库的问题,接下来要找"谁在慢"。

    第一步:看慢查询日志这是最直接的途径。所有主流数据库都有慢查询日志功能。

    MySQL:在配置文件里设置 slow_query_log = ON,long_query_time = 1(超过 1 秒的查询记入日志)。日志文件里会记录每条慢 SQL 的执行时间、锁等待时间、扫描行数。

    PostgreSQL:设置 log_min_duration_statement = 1000,超过 1 秒的语句会被记录。

    Oracle:通过 AWR 报告或者启用 SQL Trace 来捕获慢查询。

    慢查询日志是排查的第一手证据。但要注意,它只记录了"已经慢了的 SQL",不记录"为什么慢"。根因还需要进一步分析。

    第二步:看实时会话慢查询日志有延迟(日志是事后记录的)。如果系统正在慢,需要看实时状态。

    MySQL:查询 information_schema.processlist 表,能看到当前所有正在执行的 SQL、执行时长、当前状态。重点关注 Time 字段很大的那些——它们就是正在拖慢系统的 SQL。

    PostgreSQL:查询 pg_stat_activity 视图,同样能看到所有活跃会话和正在执行的查询。

    紧急情况下:如果某条 SQL 正在长时间执行并且阻塞了其他会话,可以直接 KILL 掉它,先恢复业务,再分析根因。

    第三步:用性能分析工具对于复杂场景,光看日志和会话不够,需要更系统化的分析。

    MySQL 有几个常用的性能视图:

    performance_schema.events_statements_summary_by_digest:按 SQL 模板汇总执行次数、总耗时、平均耗时。能帮你找到"不是单次慢,但执行频率极高"的 SQL。sys.schema_table_statistics:按表汇总 I/O 统计。能帮你找到"哪张表是 I/O 热点"。PostgreSQL 有 pg_stat_statements 扩展,功能和 MySQL 的 digest 汇总类似。

    经验:有时候最慢的不是某一条 SQL,而是某条 SQL 被高频执行了上万次。单次 10ms,执行一万次就是 100 秒。这种问题在慢查询日志里看不出来(因为单次没超过阈值),需要用汇总视图才能发现。

    三、分析根因——五个常见原因找到慢 SQL 后,接下来分析"为什么慢"。我总结了一个检查清单,按顺序排查,90% 的慢查询都能定位到原因。

    原因 1:没走索引(最常见)这是慢查询的头号原因。SQL 没有走索引,导致全表扫描。

    怎么判断? 用 EXPLAIN 看执行计划。如果 type 列是 ALL(MySQL)或者 Seq Scan(PostgreSQL),说明是全表扫描。

    为什么没走索引? 常见情况:

    查询条件里没有索引列。查询条件用了函数包裹索引列(比如 WHERE YEAR(create_time) = 2024,create_time 上有索引但被函数包裹后失效)。模糊查询用了前缀通配符(比如 LIKE '%张三',索引无法利用)。类型不匹配导致隐式转换(比如索引列是 INT,查询条件传了字符串,索引失效)。怎么解决? 加索引,或者改写 SQL 让索引生效。

    原因 2:走了索引,但选错了索引有时候 SQL 走了索引,但还是很慢。这是因为优化器选了"错误的索引"。

    举个例子:表上有两个索引——idx_name 和 idx_create_time。SQL 是 SELECT * FROM users WHERE name = '张三' ORDER BY create_time DESC。优化器可能选择用 idx_name 做过滤,然后用 filesort 排序。但如果数据量很大,filesort 会很慢。

    怎么判断? 看 EXPLAIN 输出里的 key 列,确认用了哪个索引。同时看 Extra 列,如果有 Using filesort 或 Using temporary,说明有额外的排序或临时表开销。

    怎么解决?

    建复合索引,同时覆盖查询条件和排序字段:idx_name_create_time (name, create_time)。用 FORCE INDEX 手动指定索引(不推荐作为长期方案,但可以用于紧急救火)。更新统计信息,让优化器重新评估执行计划。原因 3:数据量增长导致执行计划过时这是容易被忽视的原因。索引没错,SQL 也没错,但就是变慢了。

    根因:数据库的优化器依赖统计信息来做执行计划决策。统计信息是定期更新的(MySQL 默认是自动的,但更新频率有限)。如果数据量发生了巨大变化(比如某张表从 100 万行涨到 1000 万行),统计信息还没来得及更新,优化器还基于旧的统计信息做决策,执行计划就会选错。

    怎么判断? 对比"慢的时候"和"快的时候"的执行计划,如果不一样,大概率是统计信息过时导致的。

    怎么解决? 手动更新统计信息——MySQL 用 ANALYZE TABLE,PostgreSQL 用 ANALYZE,Oracle 用 DBMS_STATS.GATHER_TABLE_STATS。

    这个问题在数据量快速增长的系统中特别常见。比如电商大促后、月底结算后,数据量突然翻倍,慢查询就来了。

    原因 4:锁等待SQL 本身不慢,但在等锁。

    怎么判断? 看慢查询日志里的 Lock_time 字段(MySQL)。如果 Lock_time 占了大部分执行时间,说明是在等锁,不是查询本身慢。

    为什么锁等待? 常见情况:

    另一个事务持有了排他锁,当前事务要读或写同一行数据,只能等。间隙锁冲突(MySQL 的 REPEATABLE READ 隔离级别下常见)。外键检查时的隐式加锁。怎么解决? 参考我上一篇文章的死锁排查方法——找到阻塞源,优化事务长度和加锁顺序。

    原因 5:硬件或配置瓶颈SQL 和索引都没问题,但还是慢。那可能是底层资源不够了。

    排查方向:

    磁盘 I/O。用 iostat 看磁盘利用率。如果 iowait 很高,说明磁盘是瓶颈。内存。数据库的 buffer pool 命中率如果低于 95%,说明内存不够,频繁从磁盘读数据。连接数。连接数打满了,新连接只能排队等。CPU。如果 CPU 持续 100%,可能是并发查询太多,或者某个查询在做大量计算。怎么解决? 扩容硬件,或者优化查询减少资源消耗。有时候加一块 SSD 比调一百条 SQL 都管用。

    四、慢查询预防——日常巡检清单排查慢查询是事后救火,定期巡检才是正道。

    我的日常巡检清单(建议每周执行一次):

    查看慢查询日志汇总。统计本周慢查询的数量、趋势。如果某条 SQL 频繁出现,提前优化。检查索引使用情况。MySQL 用 sys.schema_unused_indexes 找出没被使用的索引(占空间还影响写入性能)。检查表碎片。频繁 UPDATE/DELETE 的表会产生碎片,影响查询性能。用 OPTIMIZE TABLE 定期整理。检查统计信息新鲜度。数据量变化大的表,确认统计信息是最新的。检查 buffer pool 命中率。低于 95% 就要考虑增加内存。检查连接池使用率。如果连接数经常接近上限,说明需要扩容或优化连接管理。五、总结慢查询排查就一套流程:确认慢 → 找 SQL → 看执行计划 → 查根因。

    执行计划是排查的核心工具。看懂了执行计划里的扫描方式、索引使用、排序方法,根因自然就出来了。

    预防慢查询也是日常功夫:定期巡检、及时更新统计信息、合理设计索引。

    处理过上百次慢查询问题,每次回到这套流程,问题都能解决。不需要什么高级工具,耐心和方法论就够了。

    下一篇我会讲连接池配置和连接泄漏排查,把数据库运维的几个高频问题收个尾。

    有问题评论区见。

    十五年数据库领域老炮。关注我,一起把数据库这件事搞明白。