主题
Java 数据库事务的锁等待、连接占用和吞吐应该怎样调优?
题型:调优 · 先看事务生命周期和数据库等待,再调整应用与连接池参数。
建议回答
我会先把接口耗时拆成获取连接、SQL 执行、锁等待、应用计算和提交时间,并观察事务持续时间分布、活跃事务、连接池等待、慢 SQL、死锁、回滚率和数据库 CPU/IO。目标不是让单条 SQL 看起来最快,而是在正确性不变的前提下降低锁持有时间、连接占用和业务尾延迟。
优化顺序通常是先缩短事务:把远程调用、大对象序列化和可提前完成的校验移出事务,拆小超大批次,确保索引让更新尽快定位行,并让不同代码路径按一致顺序访问热点资源。然后处理并发策略,例如用条件更新或版本号代替过大的悲观锁范围,对热点键做分片、排队或限流。最后再根据数据库最大连接数、实例数和线程池并发调整连接池,保留管理与突发余量。
隔离级别不能只为性能随意降低,必须重新验证业务不变量。对于可重试的死锁或序列化失败,应设置有限次数、退避和整体请求截止时间;重试整个事务时,事务外副作用必须幂等或延后到可靠事件链路。
关键指标
- 事务持续时间 P95/P99、锁等待时间、死锁和回滚率。
- 连接池活跃数、等待线程、获取连接时间和超时率。
- SQL 执行计划、扫描行数、数据库 CPU/IO 和日志写入压力。
- 接口吞吐、尾延迟、重试次数以及热点键分布。
关键权衡
- 增加连接数可能提高短期并发,也可能让数据库进入过载并放大锁竞争。
- 减小批次能缩短锁持有时间,却会增加往返次数和提交开销。
- 乐观锁在低冲突下吞吐较好,高冲突下可能形成重试风暴;悲观锁则会把冲突转成等待。
易错点
- 不要把连接池大小直接设成应用线程数,更不能让所有实例的连接上限超过数据库承载能力。
- 不要在事务方法内调用不受控远程服务,也不要通过无限延长事务超时掩盖慢 SQL。
- 不要把所有数据库异常都重试;约束错误、语法错误和权限错误通常不可重试。