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

Hibernate PESSIMISTIC_WRITE未锁定PostgreSQL行,数据更新异常求助

为什么PostgreSQL下Hibernate悲观锁(PESSIMISTIC_WRITE)没生效?

我来帮你分析下这个问题——你明明用了PESSIMISTIC_WRITE行锁,但两个线程更新后结果不是预期的5,而是8或7,说明两个线程都读到了初始的quantity=10,各自更新后覆盖了对方的结果,锁根本没起到阻塞作用。咱们一步步拆解原因和解决办法:

可能的原因

1. Hibernate没正确生成FOR UPDATE锁语句

悲观锁生效的核心是Hibernate要生成带FOR UPDATE的SELECT语句,这样PostgreSQL才会对目标行加排他锁。如果你的lockArticle方法用Criteria生成的SQL里没有FOR UPDATE,那等于没加锁,两个线程自然会同时读取初始值。

这种情况可能是旧版本Hibernate的Criteria锁模式配置有问题,或者锁模式没正确绑定到实体上。

2. 多余的事务注解导致事务边界混乱

你的updateArticle方法上也加了@Transactional,而doProcess本身已经是事务方法。虽然Spring默认的事务传播是REQUIRED(会加入当前事务),但如果是同一个类内部调用updateArticle,Spring的AOP代理不会生效,这个注解其实没起作用——但更关键的是,如果这个注解的传播行为被错误配置成REQUIRES_NEW,会新建一个独立事务,导致原来的锁逻辑混乱。

3. 实体类或Hibernate配置冲突

比如你的Article实体加了@Version乐观锁注解,悲观锁和乐观锁混用可能导致锁逻辑冲突;或者Hibernate的事务隔离级别被错误设置成READ UNCOMMITTED,不过PostgreSQL默认是READ COMMITTED,这个概率较低。

解决办法

步骤1:移除updateArticle的@Transactional注解

doProcess已经是事务方法,整个流程应该在同一个事务中执行,锁需要持有到事务提交才释放。updateArticle作为内部方法,不需要额外的事务注解,去掉它避免事务边界混乱。

修改后的updateArticle:

updateArticle(Article article){
    sessionFactory.getCurrentSession().saveOrUpdate(article);
}

步骤2:验证锁语句是否生成

开启Hibernate的SQL日志,查看lockArticle执行的SQL是否包含FOR UPDATE。在Spring Boot中,添加以下配置到application.properties:

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

如果日志里的SELECT语句没有FOR UPDATE,改用HQL方式加锁(比Criteria更可靠):

Article lockArticle(int id){
    return sessionFactory.getCurrentSession()
        .createQuery("SELECT a FROM Article a WHERE a.id = :id", Article.class)
        .setParameter("id", id)
        .setLockMode(LockMode.PESSIMISTIC_WRITE)
        .getSingleResult();
}

如果坚持用Criteria,可以明确指定锁到实体:

return sessionFactory.getCurrentSession().createCriteria(Article.class)
    .add(Restrictions.eq("id", id))
    .setLockMode("this", LockMode.PESSIMISTIC_WRITE) // 指定锁当前实体
    .setTimeout(10000)
    .getUniqueResult();

步骤3:手动验证数据库锁机制

在数据库客户端开两个会话,先执行:

SELECT * FROM article WHERE id = ? FOR UPDATE;

然后在第二个会话执行同样的语句,看是否会被阻塞。如果手动执行时锁生效,说明是Hibernate配置问题;如果不阻塞,检查表的id是否是主键(PostgreSQL对非主键行的锁可能有差异,但一般还是会阻塞)。

步骤4:检查Hibernate版本

如果用的是Hibernate 5.x之前的旧版本,可能存在PESSIMISTIC_WRITE的bug,升级到5.6.x或更高的稳定版本试试。

总结

大概率是Hibernate没正确生成FOR UPDATE语句,或者多余的事务注解搞乱了事务边界。先去掉updateArticle的事务注解,再验证SQL锁语句,应该就能解决问题,让两个线程按顺序执行,最终得到预期的quantity=5。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 15:34:09