本质是连接被长时间占用无法释放,根因在慢sql、锁等待或连接泄漏;需查show processlist的state和time列、innodb_trx未提交事务、threads_connected与threads_running差值,以及performance_schema归一化统计。

连接池并发过高导致SQL排队阻塞,本质不是连接数太多,而是连接被长时间占用无法释放——问题根子在慢SQL、锁等待或连接泄漏,而非max_connections设小了。
查 SHOW PROCESSLIST 看连接卡在哪一环
别只扫一眼连接总数,重点看 State 和 Time 列:
-
State = 'Sending data'且Time持续增长:不是网络传输慢,是MySQL正在组织结果集,大概率漏写了LIMIT或扫描行数过大(比如SELECT * FROM huge_table) -
State = 'Locked'或'Waiting for table metadata lock':说明有 DDL(如ALTER TABLE)或长事务没提交,把表元数据锁住了,所有后续查询全被堵住 -
State = 'statistics'或'Creating sort index':执行计划里走了文件排序或临时表,索引没覆盖ORDER BY或GROUP BY字段 - 多个连接
User相同、Host是同一应用IP、Info内容高度相似:基本可锁定是某个接口批量发起的同类慢查询
用 information_schema.INNODB_TRX 找未提交事务
高并发下最隐蔽的“连接焊死”原因,就是事务开了不关。ORM 框架里显式开启事务但异常路径没 rollback,或者 AUTOCOMMIT=0 后忘了 COMMIT,都会让连接一直挂着。
- 运行
SELECT * FROM information_schema.INNODB_TRX ORDER BY TRX_STARTED LIMIT 5,看TRX_STATE是否为RUNNING且TRX_STARTED时间远早于当前时间 - 结合
TRX_MYSQL_THREAD_ID去information_schema.PROCESSLIST查对应线程的Info,确认它在执行什么SQL - 如果
TRX_ROWS_LOCKED > 0且长时间不动,大概率是业务逻辑卡在中间没走完,而不是SQL本身慢
对比 Threads_connected 和 Threads_running
这两个值的差值,直接暴露“假忙真堵”的程度:
-
SHOW GLOBAL STATUS LIKE 'Threads_connected'是当前所有连接数(含空闲) -
SHOW GLOBAL STATUS LIKE 'Threads_running'是真正正在执行SQL的线程数 - 如果
Threads_connected接近max_connections,但Threads_running只有个位数,说明大量连接处于空闲等待状态——问题不在数据库负载,而在应用层没及时归还连接(即连接泄漏) - 反过来,如果两者都接近上限,且
State多为'Sending data'或'Sorting result',那就要立刻抓EXPLAIN FORMAT=TREE分析具体SQL
别信慢查询日志里的“慢”,先盯 performance_schema.events_statements_summary_by_digest
默认 slow_query_log 只记执行时间超阈值的SQL,但高并发下很多“慢”其实是锁等待或资源争抢造成的假象。真实耗时大户得看归一化统计:
- 运行
SELECT DIGEST_TEXT, COUNT_STAR, SUM_TIMER_WAIT FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 10 - 重点关注
SUM_TIMER_WAIT高但DIGEST_TEXT看似简单的语句(比如单表UPDATE或SELECT),极可能是被锁住反复重试 - 如果某条语句
COUNT_STAR异常高(比如每秒几百次),而应用侧并无对应高频调用,大概率是重试逻辑失控或下游服务雪崩引发的请求风暴
真正卡住连接池的,往往不是那条执行2秒的SQL,而是它背后没释放的事务、没命中的索引、或没关掉的连接。排查时盯着 State 和 TRX_STARTED 比盯着执行时间更有效——因为时间可以骗人,状态不会。











