学习MySQL慢查询排查:掌握SHOW PROCESSLIST与State状态解读
1. 项目概述这一页输出到底藏着多少信息先讲一下我个人多年来的习惯。每次线上数据库出现卡顿或者告警群里出现连接数超限我第一反应不是翻监控大屏不是打开慢查询日志而是先抓一屏SHOW PROCESSLIST看看。这个习惯养成了很久原因很简单——它是少数几个能让你直接看到数据库“此刻正在发生什么”的窗口。监控系统给你的是聚合后的曲线告诉你“平均查询响应变慢了”但不告诉你到底是哪条 SQL、哪个用户、哪台应用服务器发起的、卡在什么环节。PROCESSLIST 恰好把这些最原始的信息按连接一条条平铺在眼前。可能有人会觉得这不过是一条查看当前连接的命令没什么可深挖的。但实际在线上摸爬滚打几年后你会发现这一屏输出里能读出的信息量远超想象。State 字段里那几十种取值每一种都对应着 MySQL 内部某个具体的执行环节同一个 State 在不同场景下背后指向的根因可能截然不同。如果只是会敲命令、不会读状态那等于手里拿着一把刀却不知道往哪儿下刀排查效率会低很多。这篇文章我打算把SHOW PROCESSLIST从字段含义、State 状态解读、慢查询定位到和慢查询日志、EXPLAIN 的配合使用整个链路完整拆一遍。中间会穿插一些真实场景里踩过的坑比如“明明显示 Waiting for table metadata lock但事务表里却查不到任何锁持有者”这类隐蔽问题。希望能给被慢查询折磨的 DBA、后端开发、运维同学一些可以直接抄作业的排查思路。对于不同基础的读者我也简单说明一下边界如果你是刚接触 MySQL 的初级开发这篇文章能帮你建立对数据库运行状态的基本感知如果你已经有几年经验重点可以放在 State 状态机的深度解读和案例复盘部分这些内容更偏向实战判断。1.1 为什么它是最合适的“第一响应工具”数据库出问题时你的目标是尽快缩小排查范围。慢查询日志能告诉你历史上哪些 SQL 慢但日志有延迟默认情况下反映的是几分钟甚至几小时前的情况performance_schema信息最全能查历史快照但开启后有额外开销且表结构复杂紧急时刻写查询语句反而费时间。SHOW PROCESSLIST则是一个零配置、低开销的实时视图MySQL 内置、随时可用关键时刻敲一下就能看到当前全部连接的状态。它和监控系统是两种互补的数据形态。监控指标适合做趋势判断——比如“最近 15 分钟 P99 延迟在上升”这能帮你确定问题出现的时间范围但定位具体语句必须靠 PROCESSLIST 这类可以下钻到连接粒度的工具。我经常把监控比作天气预报告诉你今天有雨而 PROCESSLIST 是你抬头看窗外确认雨到底下没下、下多大。两者配合才是完整的排查链路但论“第一时间看清现场”PROCESSLIST 无可替代。1.2 和慢查询日志、performance_schema 的定位差异这里补充一个容易混淆的知识点SHOW PROCESSLIST只能看到当下仍处于执行状态的连接。如果一条慢查询已经执行完你看到的就是执行现场结束之后的结果需要靠慢查询日志才能把“案发过程”补全。所以它们的关系不是谁替代谁而是“现场”和“录像”的关系。performance_schema则可以理解为一套黑匣子里面的events_statements_current表能查到当前正在执行语句的完整信息events_statements_history_long能查历史语句。但它默认只记录有限数量的行且部分版本下开启会影响性能不适合作为常态化的第一排查入口。我个人的习惯是紧急问题用 PROCESSLIST 抓现场周期性的性能分析用慢日志聚合深度诊断用 performance_schema 做交叉验证。三层递进基本能覆盖 99% 的慢查询场景。2. 八列字段逐项拆解从命令输出到故障现场SHOW PROCESSLIST的输出每一行代表一个连接列名分别是 Id、User、Host、db、Command、Time、State、Info。看起来只有八个字段但每个字段在具体排查场景里都有独特的价值而且存在不少容易被忽略的细节。我按“身份字段”和“行为字段”两组来拆这样比较好记。2.1 身份字段Id / User / Host / db先给一张速查表方便大家对照记忆字段含义排查中的关键用途Id连接的唯一标识用KILL id杀掉指定会话User连接使用的账号区分业务账号和内部系统账号Host客户端 IP 和端口定位请求来自哪台应用服务器db当前操作的数据库为空表示尚未选择数据库Id 字段不用多说它是杀会话的唯一凭证。但关于 KILL 有一个很容易踩的坑KILL QUERY id和直接KILL id是两码事。前者只中断当前正在执行的语句连接本身保留后者是断开整个连接事务如果有未提交的修改会触发回滚。线上如果遇到一个大事务正在执行KILL 之后回滚可能要持续很久这段时间该连接依然会占用资源。所以下手之前先想清楚是只杀语句还是连连接一起断掉。User 字段有一个特别容易惊到新手的点你会看到system user这样的值。这个不是来自客户端的业务连接而是 MySQL 内部发起的线程比如复制线程从库拉取 binlog 的 IO 线程、回放 binlog 的 SQL 线程、事件调度器创建的线程等。看到system user不要急着当问题处理先看 State 再判断——复制线程卡住时 State 会给出明确提示比如 Waiting for master to send event 或 Slave has read all relay log。Host 字段格式是IP:端口。注意这个端口是客户端随机分配的源端口不是 MySQL 的监听端口。它的核心价值在于当多个业务共享同一个数据库实例时一条慢查询冒出来通过 Host 能立刻判断是哪台应用服务器发起的往往一个照面就能排除掉一半干扰项。我在多业务共用数据库的团队里排查时第一步永远是按 Host 分组看连接分布。db 字段表示连接当前正在操作的数据库。如果为空说明连接建立后还没执行过USE db或者语句里全程用了全限定表名。这个字段在定位连接归属时也有参考价值——某个连接如果长时间不操作任何库且 State 又是 Sleep那就很可能是应用侧连接池里闲置的“僵尸连接”。2.2 行为字段Command / Time / State / Info再看行为字段这四列才是判断慢查询的关键。Command 字段表示连接正在执行的命令类型。常见的有下面这几种Command 取值含义Query正在执行 SQL 语句Sleep连接空闲等待下一条命令Connect连接建立阶段Quit正在断开连接Binlog Dump主库向从库推送 binlogDaemonInnoDB 内部后台线程Connect和Quit都是瞬态正常情况下很难抓到。Binlog Dump在新手眼里很像异常因为它会长期存在且不执行任何业务语句但这其实是主从复制链路的正常连接有从库在就一直挂着。如果发现 Binlog Dump 数量异常多要关注的是是不是有太多从库同时连在主库上而不是急着杀掉。Time 字段——这里有一个必须强调的坑Command 类型不同Time 的含义不同。Command Sleep时Time 代表这个连接空闲了多久Command Query时Time 代表这条 SQL 已经执行了多久。很多人看到 Time 很大的 Sleep 连接就紧张觉得“数据库里有东西在跑很久”其实那只是连接池里养着的一群闲鱼。反过来如果你只看 Time 不看 Command也容易漏掉真正的慢查询——它可能已经默默跑了一百多秒只是你没意识到。State 字段是整个命令输出的核心。它描述的是这个线程当前正在做什么属于执行过程中的细粒度状态。这个字段我会用一整章来拆因为它最能反映问题也最容易误读。Info 字段展示正在执行的 SQL 文本默认最多显示一百个字符不同版本有差异所以超长的 SQL 只能看到前面一段。加FULL关键字可以看完整内容——SHOW FULL PROCESSLIST是排查慢查询时的标配。但即便如此如果 SQL 本身特别长输出的可读性依然很差。想看完整语句更稳妥的方式是查performance_schema.events_statements_current或者直接开慢查询日志。抓现场时先看 Info 前缀通常足够判断方向完整 SQL 留给后续分析。2.3 KILL 操作的正确打开方式既然讲到 Id 字段顺便把 KILL 这个操作单独说透。线上出现了必须处理的慢查询或阻塞连接时KILL 是常用的止损手段但姿势不对会引发二次故障。我总结的 KILL 操作顺序是这样的先确认这条连接到底在干什么。看 Command、State、Info区分是纯粹的慢查询还是在等锁或者是应用侧有问题导致的僵尸连接。区分 KILL 对象。只想终止一条慢 SQL用KILL QUERY id想彻底断开会话用KILL id或者KILL CONNECTION id。杀掉之前评估事务大小。InnoDB 下回滚一个大事务可能比执行它本身还慢期间连接资源不会释放所以如果判断是长事务在回滚要有心理预期别杀完就以为万事大吉。KILL 后用SHOW PROCESSLIST复查确认会话确实退出。极端情况下 KILL 也可能卡住比如底层 IO 已经无响应这时线程可能在 KILLED 状态停留很久需要从系统层继续排查。注意线上执行 KILL 前建议先在测试环境验证一下行为。不同版本、不同隔离级别下KILL 对事务的具体影响有细微差异。我见过不止一次有人想杀一条慢查询结果因为误用了KILL id直接断开了连接把应用侧连接池搞得重新建立大量连接反而放大了故障。3. State 状态机几十种状态背后的执行流水线State 字段在SHOW PROCESSLIST里是信息量最大、最容易误读的列。MySQL 官方文档列出了几十种可能的取值全部背下来不现实也没必要。实战中只需要把高频出现的状态搞透其他的遇到再查文档即可。在拆状态之前脑子里要先建立一条完整的执行流水线客户端把 SQL 发到服务端线程开始解析、优化、打开表、读数据、排序、返回结果。在这个过程中线程会依次经历多个 State每个 State 代表流水线的一个环节。排查慢查询的核心问题就是这条 SQL 卡在了流水线的哪一个环节State 就是回答这个问题的指针。3.1 正常执行链路中的常见状态下面这些状态大多数情况下属于“正在正常干活”但如果停留时间过长就值得警惕。StartingSQL 刚进入服务端正在做初始化包括权限检查、SQL 解析准备。如果大量连接同时卡在 Starting通常说明系统本身负载极高线程在排队等待 CPU 调度。Checking permissions权限校验阶段。一般瞬间完成长时间停留极其罕见真遇到的话优先怀疑 ACL 缓存异常。Opening tables正在打开 SQL 涉及的表。如果大量连接都停在这里先别急着怪“表太多了”典型原因是table_open_cache设置太小或者有人在执行 DDL 导致元数据锁竞争。Statistics优化器正在分析统计信息准备生成执行计划。卡在这里的常见原因是统计信息不准确或者系统繁忙导致读取统计信息变慢。这里我多说一句ANALYZE TABLE之后统计信息会刷新但如果表数据量级经常大起大落定期维护统计信息是个好习惯。Sending data这是最容易误读的状态。它涵盖从存储引擎读取数据、WHERE 过滤、排序、分组、临时表等一系列过程看到它时间很长应该往“这条 SQL 为什么执行这么慢”的方向去查而不是怀疑网卡慢。8.0 版本把这一阶段细分成多个更具体的状态但对存量 5.7 实例这个状态依然是最常见的“背锅侠”。Sorting result/Creating sort index正在进行排序。大概率 SQL 里带了ORDER BY或GROUP BY且没有合适的索引支撑或者sort_buffer_size设置太小导致走了磁盘临时文件。Copying to tmp table正在把中间结果写入临时表。GROUP BY、DISTINCT、UNION等操作都可能触发。重点看临时表在内存还是落盘落盘基本就意味着慢。Cleaning up收尾阶段一般很快大量堆积的话通常是系统负载过高线程调度不过来。3.2 高阻塞风险状态的识别有几类状态一旦出现基本可以判断数据库正在发生阻塞需要立刻介入而不是继续等。Waiting for table metadata lock在等元数据锁。这种状态出现意味着有其他会话持有表级元数据锁导致当前会话进不了表。常见持有者来源有两个一是长时间运行的查询或未提交事务二是一条执行完但连接未断开、事务未提交的 DDL。第二种非常隐蔽后面案例复盘会详细展开。Waiting for table flush在等FLUSH TABLES相关操作完成通常也和元数据锁有关不过触发点是 FLUSH 命令。出现时优先找是谁在执行 FLUSH以及为什么要执行。Updating正在执行 UPDATE 语句同时也在等待行锁或正在修改行数据。如果长时间停在这个状态通常是更新范围太大导致锁了大量行或者更新操作在等一个被其他事务持有的行锁。Waiting for handler commit在等存储引擎层的提交操作完成通常涉及刷盘操作。慢的原因可能是磁盘 IO 延迟高或者 redo log 写入阻塞。Locked这个状态在 MyISAM 时代很常见InnoDB 下较少出现。一旦出现代表有表级锁竞争。考虑到 InnoDB 默认行锁出现此状态通常意味着查询走了全表扫描并被锁覆盖或者存在 LOCK TABLES 语句。3.3 典型误判Sending data 并不慢在网络传输我讲一个印象很深的案例。某系统数据库每晚八点准时出现一波慢查询告警持续大概十分钟。监控看 CPU 冲到接近 100%抓SHOW PROCESSLIST一看一堆连接停在 Sending dataTime 都超过 60 秒。当时同事第一反应是查网络带宽觉得 Sending data 是“数据在发送”速度慢可能是网卡被打满了。结果网络监控显示带宽利用率不到 20%根本不卡。后来把其中一条 SQL 原样拿出来在测试库上跑了一遍EXPLAIN才发现问题出在一张大表的范围查询没走索引优化器选了全表扫描扫完一千多万行之后还需要做一次排序。Sending data 这个状态只是这段漫长执行过程在 State 字段上的“总称”真正的瓶颈全在 IO 读取和 CPU 计算上和“发送数据”没有关系。这个案例提醒我一点State 是排查线索不是最终结论。看到某个状态停留很久要结合 SQL 文本、执行计划、系统资源指标综合判断不能望文生义。同一个 State 在不同语境下可能对应完全不同的根因。比如 Opening tables 卡住可能是table_open_cache不够也可能是某个 DDL 正在拿排他元数据锁导致后续所有对该表的访问都在排队。这两种情况的处理方式截然不同前者调大缓存参数、加机器资源后者要找到 DDL 会话或者持有锁的长事务并中断它。4. 慢查询排查实操从抓现场到定位根因理论拆完了接下来进入实操环节。这一章是完整的排查流程从抓现场开始一直到确认根因、落地优化。我尽量按真实操作顺序写每一步都有明确目的。4.1 第一步抓取现场的标准动作处理慢查询的第一步永远是抓现场而不是先分析日志。抓现场的标准动作就一条SHOW FULL PROCESSLIST;注意我用了FULL。不加 FULL 时 Info 字段会被截断到很短的预览长度加了 FULL 才能看到完整 SQL 文本。执行完之后重点观察 Time 从大到小的连接把超过阈值比如 10 秒的连接单独挑出来逐个分析 State 和 Info。如果问题还在持续可以顺手查一下锁相关的信息。MySQL 5.7 时代用这三张表联合查询SELECT * FROM information_schema.INNODB_TRX; SELECT * FROM information_schema.INNODB_LOCK_WAITS; SELECT * FROM information_schema.INNODB_LOCKS;8.0 里则更推荐直接查 sys 库下的视图SELECT * FROM sys.innodb_lock_waits;这个步骤我每次排查慢查询都会做因为很多 State 出现 Waiting for table metadata lock 时单看 PROCESSLIST 根本看不出谁在持锁必须靠事务和锁表交叉验证。4.2 第二步结合慢查询日志补全时间线PROCESSLIST 是“此刻”的现场如果慢查询已经执行完抓不到了就要靠慢查询日志来还原。慢查询日志的使用有几个关键参数参数推荐配置原因slow_query_logON总开关long_query_time1默认 10 秒太宽松1 秒能捕捉到绝大多数需要优化的查询log_queries_not_using_indexesON记录未走索引的查询但开启后日志量会增大需配合轮转策略打开慢查询日志后靠人工一条条看是不现实的必须用工具做聚合分析。mysqldumpslow是 MySQL 自带的轻量工具适合快速排序pt-query-digest则功能更强能解析慢日志、PROCESSLIST 现场数据生成按执行次数、总耗时、平均耗时多维排序的报表。我团队里每周的慢查询复盘就是用 pt-query-digest 输出的报表来驱动的。这里补充一个容易被忽略的时间线技巧慢日志里的 time 字段是语句执行结束时刻。要判断“这条慢查询是不是在等别人”而不是自己慢最好把整个时间段内所有相关事务的起始时间拉出来对比。InnoDB 事务表里有trx_started字段能给出事务的真实起始时间。如果一条查询等了两分钟而持有锁的事务恰好在同一时刻启动那基本可以确定是阻塞关系而不是查询本身的计算问题。4.3 第三步用 EXPLAIN 验证执行计划定位到最可疑的 SQL 之后下一步是用EXPLAIN拿到执行计划。这一步核心关注点只有四个走没走索引、扫描了多少行、有没有临时表、有没有排序。关注项怎么判断典型优化方向索引利用type 字段不是 ALL 通常代表有索引支撑建立合适的联合索引扫描行数rows 字段远大于实际返回行数说明过滤性差优化 WHERE 条件或索引设计临时表Extra 里出现 Using temporary优化 GROUP BY / DISTINCT 逻辑排序Extra 里出现 Using filesort让 ORDER BY 走索引但注意EXPLAIN 输出的是优化器基于统计信息估算出的“计划”它告诉你优化器打算怎么做不一定反映实际做了什么。在统计信息过时或者有绑定执行计划的情况下EXPLAIN 可能给出误导结果。更精确的做法是用 8.0 新增的EXPLAIN ANALYZE它能给出实际执行时间、实际扫描行数、每个步骤的耗时。如果你的实例还停留在 5.7 且升级暂时没排期也可以通过optimizer_trace开关来获取更细粒度的优化器决策过程不过这个功能开启时对性能有一定影响只在诊断时临时开一下就好。4.4 重现场景把测试库当成你的“案发现场”最后一步是验证。拿到可疑 SQL 之后先在测试环境或者从库上重放尽量复现慢查询。复现时要注意数据量级——一张千万行的表和一张十万行的表执行计划完全可能不同。很多开发同学在本地小数据量表上跑得飞快上了生产就慢到报警就是因为数据规模带来的执行计划差异没有在测试阶段暴露。我常用的做法是从生产库通过逻辑备份或者物理备份的方式把涉及的表恢复到测试环境至少恢复最近一段时间的真实数据分布然后在这个基础上跑 SQL、看 EXPLAIN、加索引验证效果。这个过程看起来繁琐但能避免“测试环境一切正常、线上照样慢”的尴尬。5. 案例复盘三种典型的数据库卡死场景理论讲再多都不如真实场景来得直观。这一章复盘三个我在实际工作中遇到的典型案例。为了保护信息和脱敏细节做了模糊处理但排查思路和坑点完整保留。5.1 案例一连接池榨干引发的雪崩某次线上事故应用侧大量请求报连接超时数据库告警连接数达到上限。第一时间抓 PROCESSLIST发现几百个连接里绝大多数是 Sleep 状态Time 从几十秒到几百秒不等真正处于 Query 状态的反而没几个。当时团队第一反应是数据库连不上了赶紧让 DBA 调大 max_connections。但我当时建议先别动参数先看应用侧。为什么因为数据库连接数被打满往往不是数据库自身的问题而是应用侧连接池出了问题某个接口出现慢查询之后请求开始堆积应用线程等不到响应继续从连接池申请新连接池子被榨干所有请求都在排队数据库这边看到的现象就是大量空闲连接占着连接数配额实际业务查询排不上队。处理方式分两层数据库侧立刻 KILL 掉一批空闲连接释放配额缓解燃眉之急应用侧排查那个最初的慢查询根因同时把连接池的最大连接数和等待超时时间调整到合理水位。事后总结时我们达成了一个共识PROCESSLIST 里 Sleep 连接数量突增往往是应用侧出问题的信号数据库只是被动的受害方。所以巡检时看到大量 Sleep别一股脑儿全杀掉先想想为什么它们会积压在那里。5.2 案例二一条 UPDATE 的锁放大效应第二个案例业务方反馈某个模块的写操作全部超时抓 PROCESSLIST 发现一条 UPDATE 已经执行了十分钟State 是 Updating。进一步查锁表发现这条 UPDATE 的 WHERE 条件没有走索引导致全表扫描的同时对命中的行逐行加锁锁范围被放大到几乎整张表。当时表面原因是 SQL 写法问题但深挖后发现这张表的索引设计本身有缺陷WHERE 条件里的筛选列虽然建了索引但联合索引的左前缀不包含这一列优化器判断走索引的成本还不如全表扫描于是放弃索引。修正方案是在筛选列上调整索引顺序加一个合适的单列索引UPDATE 从全表扫描加锁变成精准命中少量行问题随即消失。这个案例的教训是慢查询排查不能只看单条 SQL 自身的执行时间还要关注它的锁行为影响范围。一条 UPDATE 慢不只是这条语句慢它会把后续大量对该表的操作全部堵住形成连锁反应。特别是低峰期看起来能接受的全表 UPDATE在高峰期可能就是引爆整个业务的导火索。5.3 案例三隐形 metadata lock 的完整排查过程第三个案例最隐蔽。某次收到告警数据库大量 SELECT 卡在 Waiting for table metadata lock但查INNODB_TRX却发现没有任何显式事务正在执行。这就很有迷惑性——没有长事务哪来的元数据锁排查过程是这样的先抓 PROCESSLIST发现确实有大量连接卡在 Waiting for table metadata lock状态高度一致。用sys.innodb_lock_waits查锁等待关系发现阻塞源是一条已经执行完但连接未断开的 DDL 语句。这条 DDL 的 State 显示是 Sleep但 Info 里清晰地残留着 ALTER TABLE 语句。原来执行 DDL 的客户端开启了事务但没有提交DDL 虽然执行完了事务还挂着表级元数据锁始终没有释放。处理方式其实很简单KILL 掉那个挂着 DDL 的连接元数据锁随之释放业务在几秒内恢复。但这件事给我留下的印象很深——它充分说明了“Sleep 连接就是无害的”这个判断在关键时刻可能是致命的。后来我在团队里立了一条规矩看到长时间 Sleep 且 Info 里残留 DDL 语句的连接必须作为高危目标处理。5.4 从案例里沉淀出的通用排查清单三个案例看下来可以总结出一条通用的排查路径。我把它写成了一个清单每次处理线上慢查询时按这个顺序走抓SHOW FULL PROCESSLIST按 Time 排序圈定高危连接。逐条分析 State 和 Info判断每个连接卡在哪个环节。查事务表和锁等待视图确认是否存在锁竞争或元数据锁。对可疑 SQL 做 EXPLAIN看执行计划是否合理。结合慢查询日志和监控曲线判断问题是偶发还是周期性的。找到根因后再决定是 KILL 止损、优化 SQL、调整参数还是改索引。这个清单的每一步都有明确产出不会让人卡在“下一步该干嘛”的迷茫里。上面这一步从被动救火的角度看已经够用了但如果想让系统长期健康还需要更进一步——把排查经验固化成自动化巡检能力。6. 自动化巡检把临时命令变成常态化监控手动抓 PROCESSLIST 是基本功但线上不可能每次都在出问题时恰好有人在盯。更靠谱的方式是写一个巡检任务定时抓取执行状态结合规则自动告警把“被动救火”变成“主动预防”。这一章分享一些可以直接落地的实践思路。6.1 巡检 SQL 与落库方案SHOW PROCESSLIST本质上是查询information_schema.processlist这张表所以你可以直接用标准 SQL 来做筛选比解析命令行输出要灵活得多。我常用的巡检 SQL 是这样的SELECT id, user, host, db, command, time, state, info FROM information_schema.processlist WHERE command Sleep AND time 3 ORDER BY time DESC;这条语句筛选出所有非 Sleep 状态且执行时间超过 3 秒的连接。执行时间超过 3 秒不一定全是问题但值得关注。把这条 SQL 放进定时任务里每隔 30 秒执行一次把结果写入历史表慢慢就能积累出“哪些 SQL 在什么时间段容易慢”的规律。落库表结构不用太复杂基本的字段就是上面查询的那些外加一个采集时间戳。有了历史数据之后你就可以做几个简单但实用的分析一天内各 State 的分布直方图、同一 SQL 指纹的重复慢执行次数、按 Host 分组的慢查询来源排名。这些数据不依赖第三方监控平台用一张表就能跑出来对中小团队非常友好。6.2 告警阈值的参考配置阈值怎么设我从多个线上环境的数据里总结了一套参考值不一定放之四海皆准但作为启动配置很实用指标参考阈值说明单条查询执行时间超过 3 秒告警超过 10 秒紧急处理3 秒能捕捉隐患10 秒基本是事故非 Sleep 状态超 5 秒的连接数超过 10 个触发告警大量连接同时卡住大概率有阻塞等待元数据锁的会话数出现即告警元数据锁几乎总是需要人工介入空闲连接占比超过 80% 且持续增长时关注可能是连接池配置异常除了 PROCESSLIST 本身的指标建议把线程创建速率、活跃连接数、等待锁的会话数放在一起看。这组指标联动的判断力远大于单看一个数。比如活跃连接数在涨、等待锁的会话数也在涨那大概率是锁竞争如果线程创建速率突然飙升可能是应用在频繁创建新连接需要查连接池配置。6.3 从被动救火到主动预防的转变自动化巡检最大的价值不在于省去人工盯屏而在于帮你建立基线。有了基线之后才能回答“这个状态是不是异常”这个问题。比如某天非 Sleep 连接数突然从 5 个涨到 30 个如果不知道平时只有 5 个你可能觉得这不算什么但有了历史数据的对照你就能立刻意识到系统出现了变化。当然自动化手段也有它的盲区。PROCESSLIST 的快照频率再高也只是周期性的采样两次采样之间发生的问题可能完全捕捉不到。所以我的建议是自动化巡检负责常态预防和告警人工排查的流程依然要在关键时刻手抓现场。两者结合才是完整的能力体系。最后说点个人体会。用SHOW PROCESSLIST排查慢查询看起来是入门级操作但真正发挥它的价值靠的不是背状态列表而是建立现场感。数据库的问题永远是临时发生的等你打开监控、翻完日志现场可能已经变了。PROCESSLIST 让你能站在事故发生的那一刻顺着线程状态一步步往根部摸。我排查慢查询这么多次最大的感受是大部分事故在爆发之前都有预兆而 PROCESSLIST 就是那个能帮你看到预兆的工具。建议日常巡检时多花几秒看看这个输出试着判断每一条连接在干什么日积月累你会发现自己对数据库运行状态的理解会提升一个量级。