PostgreSQL集群环境下PESSIMISTIC_WRITE行锁不生效怎么解决?
悲观写锁未生效常见原因及解决方案
首先要先确认底层执行的查询语句是否携带FOR UPDATE后缀(PostgreSQL中PESSIMISTIC_WRITE锁对应的实现就是行级写锁语句SELECT ... FOR UPDATE),可以通过在配置中开启SQL打印验证:
# application.properties 新增配置 spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true
如果打印的查询语句没有FOR UPDATE,说明锁注解根本没有生效,可按以下顺序排查:
1. 事务未生效
Spring的@Transactional注解存在多个不生效的场景,这是锁失效最常见的原因:
- 标注
@Transactional的方法不是public修饰 - 方法被同一个类内部的非事务方法直接调用(没有经过Spring代理类)
- 没有指定回滚异常范围,默认只对
RuntimeException和Error回滚,建议显式配置@Transactional(rollbackFor = Exception.class) - 项目存在多数据源时没有指定对应的事务管理器
2. JPA缓存跳过了数据库查询
如果持久化上下文的一级缓存、或者二级缓存中已经存在目标实体,JPA会直接返回缓存中的对象,不会发起数据库查询,自然不会触发数据库层面的行锁。
可以在Repository查询方法上添加缓存跳过配置,强制走数据库查询加锁:
import javax.persistence.QueryHint; import org.springframework.data.jpa.repository.QueryHints; @Lock(LockModeType.PESSIMISTIC_WRITE) @QueryHints(value = { // 跳过缓存查询数据库 @QueryHint(name = "javax.persistence.cache.retrieveMode", value = "BYPASS"), // 查询后刷新缓存 @QueryHint(name = "javax.persistence.cache.storeMode", value = "REFRESH"), // 可选:配置锁超时时间3000ms,避免长时间阻塞 @QueryHint(name = "javax.persistence.lock.timeout", value = "3000") }) public Optional<LicenseCountEntity> findById(LicenseCountId licenseCountId);
3. 复合主键映射异常
你使用了复合主键LicenseCountId,需要确认两点:
- 复合主键类实现了
Serializable接口 - 正确重写了
equals()和hashCode()方法,保证主键匹配逻辑正确
4. 低版本Spring Data JPA兼容性问题
部分老旧版本的Spring Data JPA对默认findById方法的@Lock注解支持有缺陷,可以自定义显式查询语句绕过该问题:
@Lock(LockModeType.PESSIMISTIC_WRITE) @Query("select l from LicenseCountEntity l where l.id.tenantId = :tenantId and l.id.serviceId = :serviceId") Optional<LicenseCountEntity> findLockById(String tenantId, String serviceId);
注意事项
- 锁持有的时间越短越好,避免大事务导致其他请求长时间阻塞甚至死锁
- 加锁成功后,事务提交或回滚前,其他Pod的查询请求都会被阻塞,不会读到未提交的脏数据,也无法修改该行数据
内容的提问来源于stack exchange,提问作者Anant Bhatnagar
相关产品推荐
相关产品推荐

