Spring事务中Hibernate LockAcquisitionException异常处理咨询
解决Hibernate 3.x + MySQL 5.6下并发插入的LockAcquisitionException问题
咱们先拆解下问题核心:你在接口层加了@Transactional,但并发场景下插入操作还是因为锁获取失败抛出异常,而且通用Dao.save(obj)没法修改,所以得从接口/事务层面入手调整。结合你的技术栈(Hibernate 3.x、MySQL 5.6),下面几个方案可以尝试:
1. 调整事务的锁超时时间
Hibernate默认的锁等待时间比较短,你可以通过配置让事务在获取锁时等待更久,而不是直接抛出异常。
- 全局配置:在
hibernate.cfg.xml里添加:<property name="hibernate.jdbc.lock_timeout">10000</property> <!-- 单位:毫秒,这里设置10秒等待时间 --> - 接口方法级配置:如果用的是Spring的
@Transactional,可以直接在注解里指定超时:
补充:MySQL 5.6本身有@Transactional(timeout = 10) // 单位:秒,对应10秒锁等待 public void yourInterfaceMethod(YourObj obj) { // 调用读操作和插入操作 }innodb_lock_wait_timeout参数(默认50秒),你也可以在数据库层面调整,但全局修改要考虑对其他业务的影响。
2. 实现失败重试机制
锁获取失败大多是临时性的,咱们可以在接口层给插入逻辑加重试逻辑,当捕获到LockAcquisitionException时自动重试几次。
因为不能修改通用Dao,你可以在接口实现方法里包裹重试逻辑,或者用Spring Retry(如果项目已引入依赖):
方式一:手动实现重试
@Transactional public void yourInterfaceMethod(YourObj obj) { int retryMax = 3; // 最多重试3次 int currentTry = 0; boolean success = false; while (currentTry < retryMax && !success) { try { // 执行读操作 readOperation(); // 执行插入操作 dao.save(obj); success = true; } catch (LockAcquisitionException e) { currentTry++; // 重试前短暂休眠,避免立即重试再次冲突 Thread.sleep(500); } } if (!success) { throw new RuntimeException("重试多次后仍无法获取锁,插入失败"); } }
方式二:用Spring Retry(更优雅)
如果项目兼容Spring Retry,直接在接口方法上加注解:
@Transactional @Retryable(value = LockAcquisitionException.class, maxAttempts = 3, backoff = @Backoff(delay = 500)) public void yourInterfaceMethod(YourObj obj) { readOperation(); dao.save(obj); } // 重试失败后的兜底处理 @Recover public void recover(LockAcquisitionException e, YourObj obj) { throw new RuntimeException("插入操作重试失败", e); }
注意:要确保项目里已经开启Spring Retry,比如添加@EnableRetry注解到配置类。
3. 显式锁定相关表(谨慎使用)
如果行级锁的竞争实在无法避免,你可以在事务开始时显式锁定要操作的表,确保整个事务期间表的写操作不会被其他事务干扰。因为不能修改Dao,你可以在service方法开头执行原生SQL锁表:
@Transactional public void yourInterfaceMethod(YourObj obj) { // 显式锁定目标表(WRITE锁会阻塞其他写操作和加锁读操作) sessionFactory.getCurrentSession().createSQLQuery("LOCK TABLES your_table WRITE").executeUpdate(); try { readOperation(); dao.save(obj); } finally { // 事务结束前解锁表,MySQL也会在事务提交/回滚时自动释放表锁 sessionFactory.getCurrentSession().createSQLQuery("UNLOCK TABLES").executeUpdate(); } }
⚠️ 注意:表锁粒度很大,会严重降低并发性能,只有当行级锁方案都无效时才考虑使用。
额外建议:检查事务隔离级别
MySQL 5.6默认隔离级别是REPEATABLE READ,如果你的读操作需要更严格的一致性,或者插入涉及唯一键约束,可以尝试调整事务隔离级别:
@Transactional(isolation = Isolation.REPEATABLE_READ) // 或者Isolation.SERIALIZABLE(最严格但并发最低)
注意:SERIALIZABLE会让事务串行执行,性能损失极大,需谨慎选择。
内容的提问来源于stack exchange,提问作者Sayed Uz Zaman
相关产品推荐
相关产品推荐

