线上一类非常隐蔽的数据库故障:业务接口偶尔超时,日志看不到明显慢 SQL,数据库 CPU 负载也不高,但连接数却持续上涨,连接池慢慢被打满,最后整个服务无法获取数据库连接,整体雪崩。重启应用后能短暂恢复,过一段时间故障又卷土重来。很多人的第一反应是去抓慢查询,排查半天却一无所获。本质根源,往往就是大事务。这类隐蔽故障在云栈社区里也常被提及,今天我们就完整拆解一下这个经典的线上故障案例。
一、真实线上故障现象
典型的生产环境特征表现为:
- MySQL CPU 负载平稳,没有大量慢 SQL 告警
- 数据库连接数缓慢持续上涨,不会瞬间打爆
- 应用端获取数据库连接逐步超时,接口间歇性失败
- 没有直接抛出 SQL 异常,很多业务日志看不出问题
- 重启后端应用后,连接数瞬间释放,故障临时恢复
- 流量越高,连接上涨速度越快,低峰期几乎无异常
表象上看,数据库一切如常;实际上连接正被慢慢吃光,最终服务雪崩。
二、核心根因:大事务占用连接不释放
很多开发对事务的理解是:事务就是包裹一段 SQL,执行完就提交。但这里忽略了一个关键点:事务开启后,数据库连接会一直被占用,直到 commit/rollback 才归还连接。
大事务意味着事务执行周期很长。事务内部通常包含大量业务逻辑、外部 RPC 调用、循环处理数据。数据库 SQL 执行得很快,但业务代码执行得很慢——于是数据库连接被长时间持有,无法归还到连接池。连接池里可用连接越来越少,新请求拿不到连接,自然就会出现接口超时。
重点区分:不是 SQL 执行慢,而是事务持有连接的时间太长。SQL 本身执行得很快,所以不会产生慢查询日志,这也是这类问题最难排查的地方。
三、4 类高频大事务踩坑场景
1. 事务内调用外部接口(最高频坑)
@Transactional 包裹的方法里,同步调用 RPC、HTTP 等第三方接口。数据库 SQL 一瞬间执行完毕,但外部接口响应慢或者超时卡住,事务就一直不提交。数据库连接持续被占用,白白等待外部接口返回。
2. 事务内循环批量处理大量数据
在事务里面用 for 循环逐条更新数据库,循环中夹杂业务计算。单次 SQL 很快,但循环整体耗时极长。事务长时间不提交,锁和连接都被长时间占用。
3. 事务中包含大量查询 + 复杂业务计算
查询 SQL 本身很快,但拿到数据后,在事务内部做复杂内存计算、大对象组装。数据库层面没有慢 SQL,连接却一直被占用。
4. 异常分支没有回滚,事务挂起
事务内抛出异常,但代码逻辑分支处理出错,没有触发 rollback,事务一直处于未提交状态,连接被永久占用,直到事务超时。
额外附带风险:大事务会拉长行锁持有时间,在高并发场景下极易引发锁等待、死锁,进一步放大故障影响。
四、为什么没有慢 SQL,也会拖垮数据库连接池?
慢查询指的是 SQL 执行时间长,会被记录到 slow log。而上面这种大事务的典型特征是:SQL 执行很快,但事务持续时间长。数据库连接从开启事务的那一刻起就被占用,直到 commit 才释放。连接池总数量是固定的,大量长事务持续占用连接,可用连接就被慢慢耗尽。这属于典型的连接资源泄漏类故障。
五、线上标准化排查流程
- 观察数据库连接监控:看活跃连接数、连接等待队列。如果连接持续上涨,且大量连接处于 Sleep 状态,大概率是长事务。
- MySQL 查询长事务:
SELECT trx_id,trx_started,trx_query FROM information_schema.innodb_trx;
重点看 trx_started 字段,找出长时间未提交的事务。
- 查看应用代码事务边界。找到长事务对应的接口,检查
@Transactional 方法内部,是否存在 RPC、循环、复杂计算。
- 抓取应用连接池监控。监控活跃连接、空闲连接、等待获取连接的请求数量。等待队列持续上涨,即可确认连接耗尽。
六、根治方案 & 生产落地规范
- 事务边界最小化原则(最重要):事务只包裹数据库操作,外部 RPC、HTTP、复杂业务计算全部移到事务外面。不要用一个
@Transactional 包住所有业务代码。
- 禁止在事务内循环单条更新。批量更新优先使用批量 SQL,不要循环单条 update,以此减少事务时长与锁持有时间。
- 设置事务超时时间。给事务配置超时,超时自动回滚,避免事务永久挂起占用连接。
- 数据库连接池监控 + 告警。监控活跃连接数、等待连接请求数、连接等待耗时,提前告警,不要等雪崩了才发现。
- 规范异常处理。事务内异常必须保证触发 rollback,避免异常分支导致事务无法提交或回滚。
七、总结
很多人排查数据库故障,第一时间只看慢 SQL。但长事务,才是隐藏更深的杀手。SQL 执行得很快,不代表事务就是安全的。事务的生命周期,决定了锁持有时间和连接占用时长。把控事务边界,是 MySQL 稳定性最基础,也最容易被忽视的一环。
|