长事务直接导致连接池打满,因InnoDB中每个活跃事务绑定一个连接且未提交前无法归还;30个长事务即可占满30连接池,触发超时错误。

长事务为什么直接导致连接池打满
MySQL 5.7 的 InnoDB 引擎中,每个活跃事务必须绑定一个数据库连接。事务不结束(没 COMMIT 或 ROLLBACK),连接就无法归还给池子。假设连接池大小是 30,30 个长事务同时卡住,新请求立刻触发 Connection is not available, request timed out after 30000ms —— 这不是连接不够,是连接被“钉死”了。
常见诱因包括:存储过程中漏写 COMMIT、autocommit = 0 后异常分支未回滚、SQL 执行慢(如没走索引的 SELECT * FROM orders WHERE user_id = ?)导致事务持续占用连接。
怎么快速定位正在拖垮池子的长事务
别猜,直接查 information_schema.innodb_trx 表:
SELECT trx_id, trx_started, TIME_TO_SEC(timediff(NOW(), trx_started)) AS duration_sec,
trx_state, trx_query
FROM information_schema.innodb_trx
WHERE TIME_TO_SEC(timediff(NOW(), trx_started)) > 30;
重点关注 duration_sec 超过 30 秒、trx_state = 'RUNNING' 且 trx_query 非空的记录。如果 trx_query 是 CALL proc_xxx(),基本可锁定问题在存储过程内;如果是普通 DML,则检查是否遗漏提交或被锁阻塞。
- 配合
SHOW FULL PROCESSLIST看Command = 'Query'且Time > 30的线程,确认是否正执行慢 SQL - 用
SELECT * FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep' AND TIME > 60找滞留连接,它们往往是前一个事务没结束的“后遗症” - 别忽略
TRX_WAITING_TRX_ID字段——有值说明已陷入锁等待,得顺藤摸瓜查阻塞源
Java 应用里调用存储过程时最容易踩的坑
绝大多数连接泄漏发生在 Java 层调用 MySQL 存储过程后资源未释放,不是连接池配置小,而是连接根本没“松手”。
典型错误写法:CallableStatement 执行完没关、只关了 ResultSet 漏掉 CallableStatement、手动 close() 顺序错或放在 if 分支里漏执行。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
正确姿势只有两个选择:
- 强制用
try-with-resources,确保CallableStatement和所有ResultSet自动关闭:try (CallableStatement cs = conn.prepareCall("{CALL proc_update_status(?)}")) { cs.setInt(1, orderId); cs.execute(); // 不用手动 close,作用域结束自动释放 } - 若必须手动管理,必须按
ResultSet → CallableStatement → Connection顺序在finally块中关闭(Connection.close()在连接池中实际是归还操作) - 启用 HikariCP 的
leakDetectionThreshold=60000(60 秒),一旦超时未归还,日志会打出完整堆栈,精准定位哪行代码漏关
如何从源头避免事务越拖越长
长事务不是性能问题,是设计缺陷。MySQL 5.7 对大事务容忍度极低,undo log 膨胀、主从延迟、锁冲突都会随之爆发。
关键动作不是等出事再杀进程,而是把约束前置:
- 业务逻辑中禁止在事务内做 RPC、HTTP 调用、文件读写等非 DB 操作——这些耗时不可控,一卡就是几十秒
- 批量更新时,每处理 100~500 行就
COMMIT并START TRANSACTION新事务,而不是包成一个巨型事务 - 对核心 SQL 加
SET MAX_EXECUTION_TIME = 5000(5 秒),单语句超时自动中断,防止某条慢 SQL 拖垮整个事务 - 开发阶段打开
general_log,分析真实执行路径;上线前跑压测,监控innodb_trx中事务平均时长是否稳定在 200ms 内
最易被忽略的一点:autocommit = 0 是危险开关,它不会自动开启新事务,而是让后续所有语句都堆进同一个隐式事务里。务必改用显式 START TRANSACTION + 明确 COMMIT/ROLLBACK,否则异常分支一漏,连接就永远卡在“半提交”状态。










