深夜接到报警,线上一个核心接口的响应时间从平均200毫秒直接飙到了3秒。登录数据库后我二话没说,先执行了show full processlist,很快看到一条update语句在列表里挂了132秒,State列显示Updating。顺着这条线程找到源头,是业务侧给一张大表做了个没走索引的更新,一次扫了几百万行。从发现问题到定位原因,前后不到两分钟。干这行这么多年,show full processlist一直是我排查MySQL问题时的首选动作,没有之一。
这篇文章就围绕show full processlist命令展开,讲清楚它的语法、输出结果每一列的含义、常见State状态背后的原理、真实故障场景里的排查思路,以及我实际使用中踩过的坑。无论你是刚接触MySQL的开发者,还是已经在运维一线摸爬滚打的DBA,用好这个命令,等于在数据库出问题时比别人多了一双眼睛。
1. show full processlist 是什么:一条命令看穿数据库正在干什么
1.1 它能定位的几类典型问题
show full processlist的作用,是查看当前MySQL中所有客户端连接的线程实时状态。每个连接在MySQL内部都对应一个线程,线程执行SQL时会经历多个阶段,这个命令就是把这些线程的快照直接摆在你面前。你可以把它类比成 Linux 里的ps -ef,ps看进程,show full processlist看数据库线程。
我平时用它主要解决这么几类问题:
- 有没有SQL正在长时间执行,执行的是什么内容
- 当前数据库有多少连接,这些连接来自哪里,都在干什么
- 有没有连接卡在锁等待、元数据锁(metadata lock)这类状态上
- 某个连接是不是空转了很长时间,导致连接池资源被白白占住
判断MySQL当前是“健康”还是“病危”,这个命令给出的信息往往比那些花哨的监控面板更直接。监控面板告诉你有慢查询,但它通常不告诉你慢查询是哪条;show full processlist则是把当前正在执行的所有语句和状态完整列出来,让你直接看到现场。
1.2 命令语法和 FULL 关键字的作用
命令本身非常简单:
SHOW PROCESSLIST; SHOW FULL PROCESSLIST;区别在于FULL关键字:普通SHOW PROCESSLIST的 Info 列只显示SQL语句的前100个字符,而SHOW FULL PROCESSLIST会显示完整语句。
为什么我强烈建议用带FULL的版本?因为排查慢查询时,你看到的SQL往往不只是100个字符能说完的。一条复杂的联表查询,前面部分是被过滤条件,真正的重头戏可能在后面的大段子查询或ORDER BY后面,如果只给你看前100个字符,就跟看小说只看前言差不多,根本没法判断问题在哪。所以新装环境里,能不省略就不省略,直接用SHOW FULL PROCESSLIST。
一个输出示例大致长这样:
+----+------+-----------+-------+---------+------+------------------------+---------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+------+-----------+-------+---------+------+------------------------+---------------------+ | 1 | root | localhost | test | Query | 0 | starting | select * from t | | 2 | app | 10.0.0.1 | NULL | Sleep | 120 | | NULL | | 3 | app | 10.0.0.2 | order | Query | 82 | Sending data | select ... | +----+------+-----------+-------+---------+------+------------------------+---------------------+看起来平淡无奇,但每一列都有讲究。
2. 输出结果逐列拆解:别只盯着 Info 看
很多新手拿到输出后,第一眼只看 Info 列,看到一条SQL就觉得“哦,问题在这”。但真正有经验的人会先看 Time 和 State,Info 反而放在最后确认。为什么?因为State描述的是线程当前正在做什么,Time说明这个状态持续了多久,两者结合才能判断是真有问题还是虚惊一场。
2.1 九个字段的含义与注意事项
输出一共八列,每一列我都给你拆开讲:
Id:线程ID,是MySQL为每个连接分配的唯一标识。这个值在后续kill连接时是必填参数,所有排查动作最后可能都要落到这一列上。
User:建立连接时使用的MySQL账号。注意,这里看不出是“谁”在执行,只能看出是什么账号。生产环境一般建议区分读写账号,通过这一列能快速判断是应用读账号还是管理账号引出了问题。
Host:客户端IP和端口号,格式是ip:port。这列在排查连接风暴时非常好用,如果发现几百个连接都来自同一个IP,说明某个应用实例可能连接没释放,或者是应用连接池配置出了问题。
db:当前连接选中的数据库名。如果连接建立后还没执行use dbname,这个值是NULL。这里有个细节:一个连接如果在事务里跨库操作,db列显示的是连接默认库,不代表当前语句操作的表一定就在这个库里,别只看这列做判断。
Command:连接当前正在执行的命令类型。常见的有Query(正在执行SQL)、Sleep(空闲等下一次指令)、Connect(连接刚建立)、Daemon(后台线程,如InnoDB后台线程)、Binlog Dump(主从复制拉取binlog)。其中Daemon和Binlog Dump属于正常后台活动,看到它们不用慌。
Time:线程进入当前状态的秒数。对Query来说,就是这条语句已经执行了多久;对Sleep来说,就是连接已经空闲了多久。这条是判断慢查询最直接的依据,没有之一。
State:线程当前状态。这是整张输出信息量最大的一列,后面单独展开讲。
Info:正在执行的SQL语句。如果线程没有在执行语句,这列是NULL。通过这列,你能看到具体是哪个SQL卡住了。
这里有一个我没提到的隐藏逻辑:show full processlist本身就是个快照,你看到的是执行那一刻的状态。如果一条SQL执行很快,你刷好几次都未必能看到它。所以这个命令更适合定位“持续长时间卡住”的问题,那种一闪而过的瞬时慢查询,得靠慢查询日志或 performance_schema 去查。
2.2 State 状态速查表:每个状态背后都有故事
State 列描述的是线程在执行SQL过程中的某个具体阶段。MySQL执行一条语句要经历解析、优化、执行、返回等很多环节,每个环节对应不同的状态名。我整理了一张高频出现的状态速查表:
| State | 含义 | 常见场景 |
|---|---|---|
| Sleep | 连接空闲,等待客户端发送下一条指令 | 连接池里闲置的连接,正常现象 |
| Query | 正在执行SQL语句 | 正常执行中,但如果持续很久就要警惕 |
| Sending data | 正在读取、处理、发送结果集数据 | 大查询、全表扫描、排序、临时表都可能出现 |
| Waiting for table metadata lock | 等待获取元数据锁(MDL) | 有未提交事务占着表,DDL被堵住 |
| Updating | 正在对行数据执行更新操作 | 大update范围内的行数过多 |
| Waiting for lock | 等待锁资源 | 行锁冲突或表锁竞争 |
| Locked | 等待表级锁 | MyISAM 表并发写入场景 |
| Statistics | 正在计算统计信息 | 大表的SQL优化阶段 |
| Copying to tmp table | 正在把数据拷贝到临时表 | GROUP BY、ORDER BY、去重等需要临时表 |
| Sorting result | 正在对结果集排序 | ORDER BY 没有走索引 |
| Sending to client | 正在把结果集发送给客户端 | 客户端没有及时消费结果集 |
这些状态单独看都没什么,关键是组合起来判断。比如Sending data+Time=82秒,基本可以认定是SQL本身执行慢;Sleep+Time=120秒,如果大量堆积,就得怀疑应用连接池配置有问题;Waiting for table metadata lock+Time一直在涨,几乎可以肯定有另一个事务没结束。
这里讲一下最容易被误读的Sending data。我见过不少同事一看到这个状态就以为SQL在“发送网络数据包”,其实它覆盖的阶段比字面意思广得多:包括InnoDB从磁盘读取数据到内存、做连接、做聚合、把结果集写回客户端等。大部分经典慢查询卡住时,State都显示为Sending data,所以看到它别急着下结论,结合Time和Info一起看才靠谱。
3. 实战排查:三个真实场景从现象到定位
工具讲了一堆,最终还是要落到实际问题上。我挑三个最典型的排查场景,把整个思路完整复盘一遍。
3.1 接口突然变慢:用 Time 列揪出慢 SQL
现象:某核心查询接口响应时间飙升,应用端监控告警,但数据库CPU并没有打满,负载也不高。
我登录数据库后执行show full processlist,发现有一条select语句的 Time 已经到60秒,State是Sending data,Info里是一个关联了四张表的查询。为了确认它不是“刚好被我碰见”,我等了5秒再执行一次,结果同一条语句还在,Time 变成了65秒,确认它确实卡住了。
拿到这条SQL后,我把它拷出来,直接EXPLAIN看执行计划。问题很明显:两张表之间做关联的字段在表上没有索引,驱动表的预估行数和实际扫描行数差了50倍以上。因为缺失索引,查询只能全表扫描加嵌套循环,性能自然上不去。
这个场景的关键点,在于第一次执行show full processlist后不要急着kill,而是先记录SQL内容,再结合EXPLAIN分析慢的根本原因。kill 只是止血,找到并补上索引才是治疗。补完索引再执行一次同样的SQL,时间从几十秒降到几十毫秒,问题才算真正解决。
3.2 表被锁住:从 Waiting for table metadata lock 追到源头
现象:业务方反馈某张表的插入操作全部堵住,应用日志里大量超时。
执行show full processlist后,我看到多条连接都停在Waiting for table metadata lock状态,而且等待时间都在几十秒以上。这些连接的Info里是insert、update这类语句,按理说不应该出现元数据锁等待。元数据锁是MySQL用来保护表结构不被修改的锁,正常情况下DML语句拿MDL锁都是瞬间完成,除非有别的会话用不释放。
这个时候show full processlist已经帮我把问题范围缩小到了“有会话占用了这张表的MDL锁”。但还要找出是谁占着锁。我继续查information_schema.innodb_trx,发现一个连接处于事务未提交状态,事务开始时间在一个多小时前,卡在一条select上但一直没有提交。这个连接虽然当时Sleep了,但事务还开着,它持有的MDL锁一直不释放,后面的DML和DDL全被堵住。
处置方式很明确:先联系业务确认那个事务是否还有用,确认后把对应线程kill掉,堵住的语句立刻恢复执行。这类问题用show full processlist能看到症状,但定位“元凶”往往需要配合innodb_trx和performance_schema一起用,后面会讲到组合操作。
3.3 连接数打满:分析 Sleep 连接和连接风暴
现象:数据库最大连接数设置为500,某天突然报 “Too many connections” 错误,新连接进不来。
先别急着调大max_connections,调大只是拖延问题。我用show full processlist看了一眼,发现连接数480多个,其中400多个都是Sleep状态,来自同一个应用IP段,符合连接池内连接数的上限。正常情况下,连接池里的连接会复用,Sleep是常态。但为什么这么多Sleep连接会打满?
继续观察,发现这些Sleep连接的空闲时间全部集中在几百秒到上千秒,说明连接长期没有被使用或回收。应用连接池的wait_timeout和max_lifetime配置不匹配,数据库侧把连接回收了,但连接池不知道,又在不断新建连接,一来二去就把连接数顶到上限。
处理上,先临时把max_connections适度调大缓解燃眉之急,然后针对性调整连接池的空闲连接存活时间和回收策略。这里要特别说明,show full processlist的价值在于让你看到连接的结构分布:有多少Sleep、多少Query、来自哪些IP。它帮我把问题从“连接数满”这种抽象表象,拆解成“连接池配置不合理导致连接堆积”这种具体定位,后面的优化才有明确方向。
4. 命令之外的组合拳:定位之后怎么办
show full processlist只解决“看”的问题,看完之后怎么处理、怎么做得更高效,才是进阶的关键。
4.1 kill 和 kill query 的正确用法
如果确认某条SQL有问题需要终止,语法是:
KILL [CONNECTION] 线程ID; KILL QUERY 线程ID;两者的区别是:KILL CONNECTION会连同连接一起断开,客户端需要重新建立连接;KILL QUERY只终止当前正在执行的语句,连接本身还保留。MySQL 5.7 之后推荐优先用KILL QUERY,因为它更温和,不会让连接池瞬间产生大量重连。注意,单词顺序别搞反,是KILL QUERY 12345,不是QUERY KILL。
这里有个实操坑:如果线程ID对应的连接正在执行DDL(比如ALTER TABLE),kill 掉之后,MySQL可能直接走崩溃恢复流程,回滚操作本身可能比原DDL更耗时。所以遇到DDL卡住时,先搞清楚它在等什么,而不是直接kill。如果它卡在Waiting for table metadata lock,杀掉持有锁的会话通常比杀掉DDL线程更合理。
4.2 用 information_schema.processlist 做精细化筛选
show full processlist输出的是全量快照,连接一多,刷屏是家常便饭。MySQL还提供了information_schema.processlist这张视图,用法更灵活:
SELECT id, user, host, db, command, time, state, info FROM information_schema.processlist WHERE command = 'Query' AND time > 30 ORDER BY time DESC;这条SQL能过滤出执行时间超过30秒的所有SQL,在茫茫连接里直接锁定嫌疑对象,省掉大部分人工肉眼筛选的功夫。
从实现原理上说,information_schema.processlist和show processlist读取的是同一份线程数据,但前者可以通过标准SQL做排序、过滤、聚合。我在监控脚本里就经常用它,配合GROUP BY user或者GROUP BY state统计连接分布,效果非常好:
SELECT state, COUNT(*) AS cnt FROM information_schema.processlist GROUP BY state ORDER BY cnt DESC;一句SQL就能看出当前数据库里线程都卡在哪个状态上。注意,如果环境里开启了 performance_schema,这张视图的数据来源也受相关配置影响,通常不需要额外设置。
4.3 基于 sys.session 做自动化监控
MySQL 5.7 之后自带的 sys schema 里有一个sys.session视图,可以看成是processlist的增强版。它多出了不少有用字段,比如连接到额、当前执行的语句、锁等待时间、事务持续时间等。
SELECT conn_id, user, current_statement, last_statement_age FROM sys.session WHERE command = 'Query' AND last_statement_age > 30;我的经验是:日常监控脚本一律用sys.session或information_schema.processlist,避免解析show full processlist的文本输出。原因是文本输出结果格式不固定,在命令行里人眼可以看,但程序解析起来很别扭。用SQL查询拿到的是结构化数据,直接对接告警平台或者存日志都方便得多。
在做自动化时,我常用这样一个思路:每5秒执行一次采集,把超过阈值(比如30秒)的线程信息写入监控表,持续采集几分钟后,就能看到慢SQL的分布和变化趋势。等出问题时,直接查监控表就拿到当时的完整现场,比事后补日志靠谱得多。
5. 使用中容易踩的坑和我的避坑经验
这个命令虽然简单,但实际用起来有不少细节容易出问题,我一条条列出来。
5.1 权限不够导致只能看到自己的连接
默认情况下,普通MySQL账号执行show processlist时,只能看到自己账号建立的连接。这意味着如果你的运维账号权限比较小,看到的永远是“安静祥和”的状态,别人卡得要死你这边毫无波澜。要查看所有连接,账号需要PROCESS权限:
GRANT PROCESS ON *.* TO 'ops_user';没有PROCESS权限时,还有另一种现象:能看到别人的行,但Info列显示的是unauthenticated user或者被截断的SQL内容,这种同样会严重误导判断方向。所以新装MySQL之后,给运维账号加上PROCESS权限,应该作为初始化项之一。
5.2 误把 Sending data 当成慢 SQL 直接 kill
这是我见过最频繁的误操作。前面提到过,Sending data只是线程在执行过程中的一个阶段名称,不代表真的在“发数据”。但反过来说,如果一条SQL真的慢,它多半也会卡在Sending data上。这就导致很多人养成习惯:看到Sending data就kill。
但实际上有一种常见情况:客户端设置了较小的读取超时,数据库已经把结果集准备好,正在往客户端写,客户端却不读了,连接也会停在Sending data。这种问题根源在应用侧,kill 数据库线程治标不治本,你kill完客户端重试还会复现。我的原则是:kill之前至少花10秒钟确认一下这个SQL是不是真的在服务端执行慢,而不是传输链路或者客户端消费的问题。判断依据很简单:同样的SQL在测试环境跑一遍,看它正常执行需要多久。
5.3 高频执行命令本身也会成为问题
show full processlist看起来轻量,但它在实现上要遍历MySQL内部维护的线程链表,并拷贝线程状态信息。当数据库连接数达到几千甚至上万时,这个命令本身的执行时间可能从毫秒级退化到亚秒级,频繁执行会加剧数据库压力。
我在监控方案里习惯的做法是:常规间隔不低于5秒;确认故障时手动执行可以频繁一些,但也要避免用脚本每秒钟刷一次。另外,连接数非常大的数据库,建议直接查询information_schema.processlist,并尽量用精确条件过滤,比全量show full processlist对内部锁的影响小一些。
5.4 别忽略 Command 和 State 组合看
Info列是SQL文本,Command列是这个连接在执行的命令类别,State列是SQL执行到哪个阶段,三者表面看起来独立,其实是紧密关联的一组信息。例如Command等于Binlog Dump时,State列通常是Master has sent all binlog to slave,这是主从复制正常状态,新手如果只看到“Binlog”几个字就可能误判。我自己排查问题时养成的习惯是:先把Command列过一遍,区分出正常后台活动,再集中看Query和Sleep,最后通过State和Time判断问题在哪个阶段。这套流程走下来,大部分性能问题都能圈定出清晰的排查范围。
用show full processlist这几年,最深的体会是:它是一个“定位工具”,不是一个“解决工具”。它能让你在乱成一团的数据库现场里第一时间锁定嫌疑对象,但真正解决问题,还得靠执行计划分析、索引优化、事务管理、连接池调优这些基本功。我见过不少同行只记住了一句“show full processlist”,发现问题就kill,kill完继续出问题,反复折腾却没有沉淀。正确的用法是把它当成侦查入口,每次看到异常线程后,多往前追问一步:为什么这条SQL会卡在这里?为什么连接会堆积?这样积累下来,你会慢慢养成一套属于自己的数据库排查直觉,遇到线上问题也就能越来越从容。