当数据库告警电话在凌晨两点把你吵醒监控面板上CPU曲线已经拉平应用日志里开始蹦出“connection refused”你手头最想看到的往往不是更多曲线图而是当前这一刻MySQL里面到底在跑什么。我遇到这种情况第一反应永远是同一句话SHOW FULL PROCESSLIST。这条命令在MySQL日常运维里的地位有点像急诊科的初检分诊台。它不负责给你最终诊断但能在几秒内把“当前所有连接在同一瞬间的状态”原原本本铺在你面前哪条SQL跑得最久、哪个会话卡在什么阶段、有多少连接在空转、有没有人在等锁、有没有DDL在堵路。刚接手数据库的开发者、常写SQL但没仔细看过连接状态的业务研发以及所有要靠MySQL吃饭的运维都可以从这条命令里挖出大量线索。这篇文章我打算把SHOW FULL PROCESSLIST从输出字段、典型状态、实战排查链路到自动化巡检完整讲一遍全部基于我在生产环境里的实际使用经验。1. 为什么性能告警一响我第一个执行的永远是它1.1 监控图表讲趋势processlist讲“此刻”监控面板上的CPU、QPS、活跃线程数曲线本质上是历史数据的聚合结果。它们能告诉你“出问题了”“趋势在恶化”但很难告诉你“哪一条SQL、哪一个连接、哪一种状态正在拖垮数据库”。我遇到过很多次这样的情况Grafana面板上CPU已经95%QPS冲到上万但根本不知道是哪条语句打出来的。等我想起来要查慢日志慢日志又还没来得及写全这时候唯一能立刻拿到“正在发生什么”的途径就是SHOW FULL PROCESSLIST。它给的是一个瞬时快照。你可以把它理解成宿管查寝推开门的一瞬间谁在睡觉Sleep、谁在赶作业Query、谁在门口等浴室Locked一眼扫过去清清楚楚。这种“现场感”对于性能问题的初步定位太有价值了尤其是深夜被叫起来的时候人本来就不清醒一个能直击关键点的命令比翻半天监控图高效得多。1.2 一个FULL字母决定你看到的是完整SQL还是残缺片段很多刚接触MySQL的开发者知道SHOW PROCESSLIST但习惯性不写FULL。这个习惯在排查性能问题时很吃亏。不带FULL时输出里的Info列正在执行的语句会被截断最多只看得到语句最前面的100个字符。一条几十行的UPDATE、一条带大IN子句的SELECT截断之后根本看不出全貌。而SHOW FULL PROCESSLIST会把完整语句显示出来这也是我为什么在任何场景下都坚持用FULL版本。另外一个小技巧在mysql命令行客户端执行时可以写成SHOW FULL PROCESSLIST\G用垂直输出替代表格输出。遇到字段内容长、行数多的时候垂直输出一列一行眼睛扫起来比横向表格舒服很多也避免了终端自动折行造成的视觉混乱。1.3 权限门槛没有PROCESS权限你只能看见自己的会话SHOW FULL PROCESSLIST输出的是所有连接的会话信息所以它需要相应的权限。只有PROCESS权限的账号才能看到所有用户的线程普通业务账号执行这条命令时只能看到自己当前的那一个连接排查起来几乎等于抓瞎。如果你执行的时候报ERROR 1227 (42000): Access denied; you need (at least one of) the PROCESS privilege(s) for this operation说明当前账号权限不够。生产环境我通常会给DBA账号单独加上PROCESS权限业务账号不给。这一点在权限管理规范里值得专门写进去因为总有开发同学拿着业务账号连上主库然后一脸疑惑地问我“为什么只有一行”。2. 逐行拆解输出字段真正读懂State和Time排查才算入门2.1 先认识这张表的八个字段SHOW FULL PROCESSLIST的输出分为八个字段每一个都有明确含义。我把它们整理成一张对照表排查时按表取用字段含义排查价值Id会话/线程IDKILL命令的目标也是后续关联查询的线索User发起该连接的用户名区分是应用账号、DBA账号还是系统账号Host客户端地址和端口能定位是哪台应用服务器发起的请求db当前连接的默认数据库快速锁定问题落在哪个库Command连接当前的大类状态区分忙碌Query和空闲SleepTime线程进入当前状态已持续的秒数和Command、State组合起来才有意义State正在执行动作的细粒度状态最值得深究的字段后面重点讲Info正在执行的SQL语句内容FULL模式下展示完整文本Sleep状态为NULL2.2 Command字段先把“忙”和“闲”分开Command字段描述的是会话大类状态。最常见的两个值就是Query和Sleep。Query表示这个连接正在执行请求Sleep表示连接空闲等待下一条指令。一个连接池开得很大的应用Sleep会话数量动辄几百这是正常现象不代表数据库有问题。很多新手看到一堆Sleep连接、Time又很大就开始紧张其实先分清Command再谈别的。实际输出里还可能看到Binlog Dump、Connect、Quit、Killed等。其中Binlog Dump通常是复制线程或者并行复制相关System user开头的行往往是后台线程这些都不是业务连接排查业务问题时可以先放到一边。2.3 State字段真正决定下一步去哪里的关键State是MySQL线程当前正在执行动作的细粒度状态也是整个命令里信息量最大的部分。一个查询在执行过程中会经历多种状态我们最终抓到的只是它刚好停住的“那一帧”。高频出现的几个状态我逐个说下看到它们时的判断思路。Sending data怕是被误解最深的一个状态。字面意思是“正在发送数据给客户端”但实际它涵盖了查询执行过程中读取数据、检索匹配行、临时表排序、生成结果集等一大段工作。大量时间停留在这个状态更常见的原因是查询在执行阶段本身就慢比如全表扫描、filesort、临时表而不是网络传输卡住了。看到这个状态第一反应应该是“这条查询的执行计划可能有问题”。Waiting for table metadata lock是线上事故的高发状态后面我会专门展开。它表示当前线程正在等待元数据锁MDL通常意味着有事务未提交另一个会话正在对该表执行DDL操作或者反过来被一个未结束的长事务挡住了。Updating表示线程正在找到需要更新的行并准备执行更新Statistics是查询正在计算执行计划Creating sort index是正在为ORDER BY或者GROUP BY创建排序结构Copying to tmp table是正在把中间结果写入临时表。这些都是正常执行过程的某个阶段要结合Time字段判断是否卡得过久。还有一个状态叫Waiting for table flush看到它一般要想到FLUSH TABLES命令或者备份工具。典型场景是某个人用FLUSH TABLES WITH READ LOCK准备做一致性备份结果一直没释放锁后续所有涉及表的DML全部排队。2.4 Time的两个判断原则结合State、区分SleepTime字段表示当前状态已经持续了多少秒。拿到一个大的Time值千万不要直接得出结论说“这条SQL很慢”一定要遵循两个原则。第一先看Command。如果Command是SleepTime再大也只说明这个连接空闲了很久和SQL性能一点关系都没有。如果Command是QueryTime才代表语句真正跑了好久才值得紧张。第二再把State带进来。Time大且State是Sending data基本可以判定是查询执行本身耗时长Time大但State是Waiting for table metadata lock或者Waiting for lock那大概率是锁问题SQL本身可能飞快只是拿不到资源。同样的Time数值背后的处理方式完全不同这就是State和Time必须组合读的原因。3. 一次慢查询实战如何在几十行输出里圈出真凶3.1 场景还原高峰期CPU被打满应用开始拒绝连接某次值班某订单系统的MySQL实例在晚高峰突然CPU飙到95%以上应用侧很快出现大量连接超时和拒绝连接。我登上去执行SHOW FULL PROCESSLIST屏幕上刷出了好几页会话。如果这时候慌慌张张一条条去看效率很低。我的套路是先快速分类先过滤掉Command为Sleep的行只看Query然后在Query里按Time降序扫。结果一眼就看到七八条相同模样的SQL全部指向同一张表SELECT order_id, order_amount, create_time FROM order_detail WHERE user_id 123456 ORDER BY create_time DESC LIMIT 20;这些查询的State清一色是Sending dataTime从几十秒到一百二十秒不等。十几条大慢查询同时压在这张表上CPU和磁盘IO被吃满连接池也因此耗尽后来的正常请求全被拒之门外。3.2 用EXPLAIN复核确认是执行计划的问题processlist能告诉你“谁是凶手”但要确认“它为什么是凶手”还得再看执行计划。我挑其中一条SQL在原库执行了EXPLAINEXPLAIN SELECT order_id, order_amount, create_time FROM order_detail WHERE user_id 123456 ORDER BY create_time DESC LIMIT 20;结果很典型type是ALLrows估算上千万Extra里有Using filesort。这说明两个问题一是where条件的user_id字段没有索引导致全表扫描二是order by的create_time也没有索引可用MySQL只能额外做一次文件排序。因为这张表已经积累了上千万行全扫描加排序让单条查询耗到了十几秒甚至更久并发一高CPU就撑不住了。3.3 处理顺序先止损再根治当时线上不能慢慢等我先用processlist里的Id字段对最耗时的会话执行了KILL让CPU立刻降下来保住其他正常业务KILL 23456; KILL 23457;这里有个细节要注意对正在执行大查询的会话执行KILL后线程并不是瞬间消失它需要时间清理现场。所以执行完KILL后别急着反复执行等几秒再看processlist确认会话是不是真的退出了。止损到位后才是根治。我评估了这条查询的访问模式按user_id查详情、按create_time倒序取最近记录于是加了联合索引ALTER TABLE order_detail ADD INDEX idx_user_time(user_id, create_time);加完索引再看EXPLAINtype从ALL变成refrows从千万级别降到几百Extra里的Using filesort消失了。同样一条SQL执行时间从一百多秒降到几毫秒。这次排查从登录到建完索引前后大概十几分钟如果没有SHOW FULL PROCESSLIST第一时间的定位光靠监控图猜可能还得加班到天亮。4. 锁与元数据锁processlist里最容易误判的高危状态4.1 Metadata Lock堵车一个未提交事务引发的全线拥堵有一种线上事故表面症状极其迷惑SHOW FULL PROCESSLIST里一排SELECT全部卡在Waiting for table metadata lock看起来像是查询把表搞堵了。但真正要处理的对象往往根本不在这些SELECT里。我复盘过一个类似的案例某开发者在前一天晚上手动开了事务执行了一条UPDATE然后直接关掉了客户端。MySQL的autocommit没有被触发那个事务就一直挂在系统里持有表上的元数据锁。第二天上午有人对同一张表执行ALTER TABLE添加字段DDL需要拿MDL写锁但表上的旧锁没释放DDL只能排队。之后所有涉及这张表的正常SELECT也会因为DDL在前面排队而被连带阻塞。最终现象就是全业务卡死processlist里全是等待MDL的SELECT。排查思路很清晰先别急着处理那些等待状态的会话去问一句“谁持有锁”。查这个最直接的表是information_schema.innodb_trxSELECT trx_id, trx_state, trx_started, trx_mysql_thread_id, trx_query FROM information_schema.innodb_trx ORDER BY trx_started ASC;找trx_started最早、事务状态还是RUNNING的那一行trx_mysql_thread_id就是持有锁的连接ID再回到processlist里确认它当前在干什么。确认是无用事务后执行KILL后面所有排队会话自然就解开了。这个案例里问题的根子在于“开了事务不提交也不回滚”processlist只是把结果暴露出来。4.2 行锁等待用sys.innodb_lock_waits直接看阻塞关系MDL之外另一个常见场景是行锁竞争。当你看到processlist里某条UPDATE或者DELETE长时间处于Waiting for handler commit、Updating同时又找不到明确的慢SQL十有八九是在等行锁。MySQL 5.7以上自带的sys schema里有个视图可以直接把锁等待双方一次列出来叫sys.innodb_lock_waitsSELECT * FROM sys.innodb_lock_waits\G输出里能看到waiting_pid、waiting_query、blocking_pid、blocking_query等字段甚至直接告诉你谁堵了谁。有了这张图处理就不再是盲人摸象确认阻塞事务是否还有价值没有就KILL它。这里我想强调一点行锁等待和MDL等待经常同时出现如果你只盯着processlist里的State看可能会把“等待方”当成“源头”一定要用锁等待视图把上下游找清楚再动手。4.3 被KILL之后State一直是Killed别重复提交KILL还有一类会话让人心里发毛明明执行了KILL再次SHOW FULL PROCESSLIST看到它State还是KilledTime越来越大。这时候不要慌也不要重复执行KILL。State显示Killed说明MySQL已经接受了终止指令但线程还没有完全退出正在做回滚或清理工作。如果被KILL的是一个超大事务回滚过程可能要持续几分钟甚至更久期间Time会继续涨。这是正常现象。反复执行KILL没有意义反而可能干扰清理过程。正确做法是观察等待等线程彻底从列表里消失。极端情况下如果回滚太慢影响了主库可用性那已经属于紧急干预范畴需要结合备份、主从拓扑做更细致的评估不是简单多加一条KILL能解决的。5. 别只靠肉眼看用information_schema把processlist变成可控的查询5.1 为什么要从命令行走向SQL查询SHOW FULL PROCESSLIST的输出是表格但你要在几十行里做过滤、排序、分组肉眼实在太慢。实际上同样一份数据在information_schema.processlist表里也有一份而且可以写SQL来查。只要你有PROCESS权限这条路径比直接看命令行输出灵活得多。有一种常见误解觉得information_schema.processlist是一张实实在在的表。准确地说它是一个系统视图每次查询时动态采集当前线程信息。所以不要在事务里长时间对它加锁查询也不要在高并发下做过于频繁的轮询它是一个诊断工具不是业务数据源。5.2 我一直在用的几条巡检SQL下面这几条是我日常排查和自动化脚本里高频使用的可以直接复用。第一条找出当前耗时最长的非空闲会话SELECT id, user, host, db, command, time, state, LEFT(info, 100) AS info_prefix FROM information_schema.processlist WHERE command Sleep ORDER BY time DESC LIMIT 20;这条SQL先把Sleep连接全部过滤掉剩下真正在干活的Query按耗时排序。很多场景下问题SQL一眼就能扫出来。第二条统计Sleep会话的堆积情况排查连接池问题SELECT user, host, COUNT(*) AS sleep_cnt, MAX(time) AS max_idle_seconds FROM information_schema.processlist WHERE command Sleep GROUP BY user, host ORDER BY sleep_cnt DESC;如果一个来源的Sleep连接数一直在高位应用连接池可能存在连接泄漏或配置过大正常业务不应该是这种形态。第三条按SQL指纹做并发聚合看同一类SQL打了多少并发SELECT db, LEFT(info, 100) AS sql_prefix, COUNT(*) AS running_cnt, MAX(time) AS max_time FROM information_schema.processlist WHERE command Query GROUP BY db, sql_prefix ORDER BY running_cnt DESC LIMIT 20;第四条专门盯异常等待状态SELECT id, user, host, db, state, time, LEFT(info, 120) AS info FROM information_schema.processlist WHERE state LIKE Waiting% AND command Sleep ORDER BY time DESC;这四条基本覆盖了慢查询定位、连接堆积、并发突刺、锁等待四类最常见的排查场景。5.3 processlist只是瞬时快照历史回放还得靠慢日志这里要泼一盆冷水processlist无论如何加强都只是“当前瞬间”的快照。等你把库里所有连接扫一遍刚才那条SQL可能已经执行完了它不会留在任何输出里。所以排查、复盘一个已经过去的问题光靠processlist没用必须配合慢查询日志。我建议生产环境把long_query_time设成1秒有必要的业务甚至可以设到0.5秒这样正常慢SQL都会被记录下来。同时建议保留performance_schema.events_statements_history_long或者第三方采集链路因为慢日志记录的是“执行结束”的语句而processlist能记录“正在执行”的瞬间两者联合起来才能还原完整现场。5.4 一个简单的轮询巡检思路既然processlist能查到我倾向于在关键业务实例上做一个轻量轮询用crontab每10秒执行一次巡检SQL把结果追加到本地文件或一张分析表。出问题时翻历史快照就能看到问题SQL是几点几分出现的、当时并发多少、持续了多久。轮询间隔不建议小于10秒太密对数据库有额外开销而且从排查粒度来说10秒足够捕捉大多数慢查询。6. 实战多年总结这些关于processlist的经验和坑值得你知道6.1 Time大不等于SQL慢先分Command再下结论这是我在文章里反复强调的一点单独拿出来再说一次。曾经有同事半夜打电话说SHOW FULL PROCESSLIST里有一堆Time超过几百秒的连接以为是慢查询把库拖垮了。我让他先看Command结果全是Sleep。那些空闲连接只是连接池里没释放的残留并不占用数据库CPU也不是性能瓶颈。真正要关注的是Command为Query、State不是Sleep、Time又大的组合。6.2 KILL和KILL QUERY别用混KILL和KILL QUERY是两个语义。KILL QUERY id终止当前正在执行的语句但保留连接本身连接还可以继续为客户端服务KILL id则直接断开整个会话。如果某个连接正在跑一个大事务KILL会触发事务回滚回滚本身可能很耗时。如果只是想停掉一条误执行的普通SQL保留连接用KILL QUERY更温和。这个区别在线上操作时特别重要用错了容易造成连接浪涌。6.3 终端输出被折行用\G反而更快排错的时候时间宝贵别在终端排版上浪费时间。我习惯执行SHOW FULL PROCESSLIST\G让每条会话的字段竖排显示。即使某个SQL文本很长也不至于被终端折行切成好几段排查体验会好很多。在自动化脚本里如果不想加\G可以用mysql -e SHOW FULL PROCESSLIST -B批处理模式拿到的输出更适合后续处理。6.4 连接数满了依然有救有过这样的窘境max_connections被大量Sleep和慢查询占满新连接全部被拒绝想执行SHOW FULL PROCESSLIST都连不上去。这时候要记住一个特性即使连接数达到上限拥有CONNECTION_ADMIN或SUPER权限的账号通常还能保留一个连接名额。所以生产环境的DBA账号不要随便发放权限关键时刻它就是钥匙。很多云数据库控制台里也有“会话诊断”功能背后的实现思路依然绕不开processlist只是帮你做了可视化。6.5 别把后台线程和复制线程当成业务连接误处理processlist输出里会出现System user开头的行比如Binlog Dump线程它们看起来也是连接实际上是MySQL内部线程或者复制架构里的线程。业务排查时别看到它们就顺手KILL容易把主从复制弄出问题。如果发现复制线程的Time一直在涨应该去查复制状态、网络延迟而不是简单终止线程。这条经验是拿真金白银的教训换来的操作任何Session前先想清楚它的身份。最后聊一个我自己保留多年的小习惯。每次执行SHOW FULL PROCESSLIST我不急着看Time最大的那一行而是在心里过一遍固定的顺序先扫Command分类再扫State筛选最后才聚焦到具体SQL文本。这套顺序看起来简单但能过滤掉大量干扰信息让我在几十行输出里快速找到真正值得动手的地方。希望这篇文章能让你下次面对数据库告警时更从容地敲下第一条命令。