如何解决未显式加锁却频繁抛出PessimisticLockingFailureException的问题
PessimisticLockingFailureException异常分析与解决方案
异常背景
相关异常堆栈如下:
org.springframework.dao.PessimisticLockingFailureException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.PessimisticLockException: could not execute statement at org.springframework.orm.hibernate5.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:240) ~[spring-orm-5.2.9.RELEASE.jar:5.2.9.RELEASE] at org.springframework.orm.hibernate5.HibernateTransactionManager.convertHibernateAccessException(HibernateTransactionManager.java:804) ~[spring-orm-5.2.9.RELEASE.jar:5.2.9.RELEASE] at org.springframework.orm.hibernate5.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:639) ~[spring-orm-5.2.9.RELEASE.jar:5.2.9.RELEASE] at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:743) ~[spring-tx-5.2.9.RELEASE.jar:5.2.9.RELEASE] at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711) ~[spring-tx-5.2.9.RELEASE.jar:5.2.9.RELEASE] at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:633) ~[spring-tx-5.2.9.RELEASE.jar:5.2.9.RELEASE] at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:386) ~[spring-tx-5.2.9.RELEASE.jar:5.2.9.RELEASE] at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118) ~[spring-tx-5.2.9.RELEASE.jar:5.2.9.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.9.RELEASE.jar:5.2.9.RELEASE] at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95) ~[spring-aop-5.2.9.RELEASE.jar:5.2.9.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.9.RELEASE.jar:5.2.9.RELEASE] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) ~[spring-aop-5.2.9.RELEASE.jar:5.2.9.RELEASE] at com.sun.proxy.$Proxy168.delete(Unknown Source) ~[?:?]
对应的业务代码如下:
class Suppress { Job job = serviceImpl.getJob(jobId); String name = job.getJobName(); //do something with name serviceImpl.delete(job) } @Transactional(propagation = Propagation.REQUIRED, readOnly = true, rollbackFor = Throwable.class) class ServiceImpl{ getJob(int id){ daoImpl.get(id) } delete(Job job){ daoImpl.delete(job) } }
根因分析
- 事务配置错误:ServiceImpl类上标注了
readOnly = true的全局事务配置,delete方法未单独指定事务属性,继承了只读配置。删除属于写操作,运行在只读事务中时,数据库无法分配写锁,和其他持有该行读锁的事务产生冲突触发异常。 - 事务边界拆分不合理:Suppress类中先后调用getJob、delete两个独立事务方法,第一个只读事务加载Job对象后会提交并释放读锁,第二个删除事务需要重新申请写锁,两个事务的间隙内如果有其他事务修改、锁定了对应行记录,就会触发锁冲突。
- 实体状态异常:第一个事务加载的Job对象在事务提交后变为游离态,delete方法执行时需要先将游离态实体重新关联到持久化上下文,关联过程中如果遇到行锁占用、版本冲突也会触发锁异常。
解决方案
- 修正事务属性配置:给delete方法单独添加事务注解,覆盖类级别的只读配置,保证写操作运行在可写事务中:
@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Throwable.class) delete(Job job){ daoImpl.delete(job) } - 合并事务边界:将查询、操作、删除的逻辑封装到同一个事务中执行,避免两个独立事务之间的锁间隙被其他请求抢占。可以在ServiceImpl中新增组合方法,或者在外层调用逻辑上加事务注解,保证整个操作链路在同一个事务内完成。
- 显式配置锁策略:如果存在较高并发修改同一条Job记录的场景,可以在查询阶段就显式申请悲观写锁,避免后续执行写操作时出现锁冲突:
getJob(int id){ daoImpl.get(id, LockModeType.PESSIMISTIC_WRITE) } - 新增重试机制:针对偶发的锁冲突,可以给删除逻辑添加3-5次的有限重试,捕获
PessimisticLockingFailureException后间隔100-300ms再重试,提升并发场景下的操作成功率。
内容的提问来源于stack exchange,提问作者Hemanth
相关产品推荐
相关产品推荐

