You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

高负载服务下Hibernate+MySQL事务死锁问题排查求助

高负载场景下MySQL死锁问题分析

问题背景

在高负载服务中,周期性遇到数据库死锁问题,死锁日志如下:

------------------------
LATEST DETECTED DEADLOCK
------------------------
2023-03-06 22:48:18 0x7fa6a24af700
*** (1) TRANSACTION:
TRANSACTION 209985062, ACTIVE 2 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 1134110, OS thread handle 140353648719616, query id 13926656547 localhost 127.0.0.1 userdb updating
update element set tags_names_string='XXXXX', genres_names_string='XXXX', description='YYYY' where id=370443
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1313 page no 9937 n bits 120 index PRIMARY of table user.element trx id 209985062 lock_mode X locks rec but not gap waiting
*** (2) TRANSACTION:
TRANSACTION 209985053, ACTIVE 3 sec starting index read
mysql tables in use 1, locked 1
551 lock struct(s), heap size 90320, 985 row lock(s), undo log entries 545
MySQL thread id 1134109, OS thread handle 140353664120576, query id 13926657948 localhost 127.0.0.1 userdb updating
update element set last_update='2023-03-06 22:48:15.912' where id=370443
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 1313 page no 9937 n bits 120 index PRIMARY of table user.element trx id 209985053 lock mode S locks rec but not gap
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1313 page no 9937 n bits 120 index PRIMARY of table user.element trx id 209985053 lock_mode X locks rec but not gap waiting
*** WE ROLL BACK TRANSACTION (1)

问题发生在两次连续更新同一行记录时,每次更新均通过主键过滤单条记录。已在Java代码中对表更新做了同步处理,但仍出现死锁,代码如下:

SpringSefviceClass (singleton):

def synchronized updateElement(ElementMessage msg) {
  Element.withNewTransaction {
    def e = Element.get(e.id)
    e.xxx= msg.xxx
    ... 
    e.save(flush: true)
  }
  sleep(400)
}

添加了sleep来确保数据库事务完成,虽减少了错误但仍会发生,sleep(10000)可消除错误但性能太差。确认同一时间只有一个线程执行该方法,原以为Java中事务结束前DB事务已完成,但日志显示即使sleep后仍未完成。

环境信息

  • 表含50万条记录
  • Percona Server 5.7.35-38-log
  • Java 11,Spring 5.3.25,Hibernate 5.6.11.Final
  • DB分配约40GB内存,使用高速SSD,服务器负载约40%
  • 使用Hibernate的dynamicUpdate选项,未修改索引列

核心疑问

为何事务提交后MySQL仍对记录进行操作,进而导致死锁?


问题根源与解决方案

根源分析

  1. 事务提交与锁释放的时序差
    Java事务块执行完毕≠MySQL事务立即提交并释放锁。Hibernate事务提交涉及多环节延迟:

    • flush仅将变更同步到JDBC缓存,并非立即发送到数据库
    • JDBC驱动可能批量发送语句,存在网络或数据库端处理延迟
    • InnoDB提交事务时需完成redo/undo日志写入、锁释放等操作,高负载下这些步骤存在短暂延迟,锁并非原子性释放
  2. JVM同步无法覆盖数据库事务周期
    synchronized仅保证JVM层面线程互斥,但Java线程同步和数据库事务提交是独立流程,无法严格对齐时序。即使单线程执行方法,前一个线程的数据库事务可能还未完成锁释放,后一个线程的数据库请求已到达MySQL,触发锁等待。

  3. 锁升级引发的循环等待
    从死锁日志可见:

    • 事务2持有该记录的S共享锁,同时等待升级为X排他锁
    • 事务1等待该记录的X锁
      S锁来自Element.get(e.id)的读操作(默认隔离级别REPEATABLE READ下会加读锁),后续save()需要升级为X锁。高负载下,前一个事务的S锁未释放,后一个事务的X锁请求已到达,同时前一个事务尝试升级X锁,形成循环等待触发死锁。

解决方案

  • 调整事务隔离级别:若业务允许,将隔离级别改为READ COMMITTED,InnoDB在此级别下读操作使用快照读,不加S锁,避免锁升级冲突
  • 显式加排他锁查询:查询时直接用Element.lock(e.id)或HQL的select ... for update获取X锁,跳过锁升级步骤
  • 缩小事务范围:尽量精简事务内操作,缩短锁持有时间
  • 移除sleep操作:sleep是低效不可靠的方案,应通过数据库锁机制或事务优化解决问题
  • 检查连接池配置:确保连接池事务提交机制正确,避免连接复用导致的事务状态残留

内容的提问来源于stack exchange,提问作者demon101

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 05:46:09