长事务会推高threads_connected,因其未提交前持续占用连接、无法被回收或超时断开;大量堆积将迅速耗尽max_connections。

为什么长事务会直接推高 Threads_connected?
长事务本身不等于长连接,但 MySQL 在事务未提交或回滚前,会一直持有该连接,状态保持为 Query 或 Locked,不会进入 Sleep。这意味着连接无法被连接池回收,也无法被 wait_timeout 自动断开——它卡在事务生命周期里,持续计入 Threads_connected 计数。一旦有几十个几秒到几分钟的长事务堆积,max_connections 很快就满。
用 innodb_lock_wait_timeout + lock_wait_timeout 控制锁等待时长
很多“长事务”实际是卡在锁等待上(比如 UPDATE 等不到行锁),而不是业务逻辑真要跑那么久。这类情况必须靠超时机制打断:
-
innodb_lock_wait_timeout控制 InnoDB 行级锁等待上限(默认 50 秒),设太低可能引发频繁死锁回滚,设太高会让连接挂更久;生产建议设为10~30 -
lock_wait_timeout控制元数据锁(MDL)等待,比如 ALTER TABLE 期间其他查询被阻塞;默认 31536000 秒(1 年),必须改!建议设为30或60 - 两者都支持 SESSION 级设置,应用可在开启事务前执行:
SET SESSION innodb_lock_wait_timeout = 15;
强制事务粒度拆分 + 应用层超时兜底
数据库层无法自动拆分业务逻辑,但可以配合应用层把“一个大事务”变成“多个小事务”,并用超时切断失控路径:
- 避免在事务内调用 HTTP、RPC、文件读写等外部依赖;这些操作失败或延迟会直接拖住整个事务
- 对循环批量处理(如导入 10 万条记录),每 100~1000 条 commit 一次,不要包进单个 BEGIN…COMMIT
- 应用侧必须设置数据库操作超时(如 HikariCP 的
connection-timeout、transaction-timeout),且该值应明显小于wait_timeout(例如设为 10 秒),否则连接池线程会卡住等 DB 响应,间接放大连接占用
监控与自动拦截:用 performance_schema 抓住真凶
光靠 SHOW PROCESSLIST 很难区分“合理长事务”和“异常长事务”。要用 performance_schema 定位真实耗时来源:
- 查当前运行超 30 秒的事务:
SELECT THREAD_ID, TIMER_WAIT/1000000000 AS sec, SQL_TEXT FROM performance_schema.events_statements_current WHERE CURRENT_SCHEMA = 'your_db' AND TIMER_WAIT > 30000000000 ORDER BY TIMER_WAIT DESC LIMIT 5; - 结合
threads表确认是否前台连接:SELECT THREAD_ID, TYPE, USER, PROCESSLIST_TIME FROM performance_schema.threads WHERE PROCESSLIST_TIME > 30 AND TYPE = 'FOREGROUND'; - 关键点:不要只看
Time字段(来自 SHOW PROCESSLIST),它只反映空闲时长;真正要盯的是语句执行耗时(TIMER_WAIT)和事务开启时长(需关联events_transactions_current)
长事务的隐蔽性在于它不报错、不中断,只是悄悄吃掉连接资源。最危险的是那些没设超时、又嵌套了外部调用的事务——它们可能存活数分钟甚至数小时,而你还在等应用日志报错。











