SQL Server中SELECT FOR UPDATE的合适查询提示选择问询
针对Spring + SQL Server + Hibernate锁问题的解决方案
1. 关于PESSIMISTIC_READ的选择是否恰当
不恰当。原因有二:
LockModeType.PESSIMISTIC_READ对应SQL Server的共享锁(S锁),多个事务可同时获取同一行的S锁,根本无法阻止并发更新导致的静默覆盖——两个事务都能读到同一版本数据,后续更新仍会互相覆盖,完全达不到防静默更新丢失的目的。- 原
SQLServer2005Dialect默认给FOR UPDATE查询添加holdlock提示,会将S锁保持到事务结束,批量查询时极易触发SQL Server的锁升级(行锁→页锁→表锁),这正是你方遭遇大量LockAcquisitionException的核心原因。
2. 自定义Hibernate方言的方向是否正确
完全正确。Hibernate的默认方言锁提示逻辑是通用实现,未必适配特定业务场景。通过继承SQLServer2012Dialect并重写锁提示生成逻辑,是解决这类方言适配问题的标准且有效的方案。
3. 正确的查询提示与配置
要满足你方列出的4项核心需求,需结合自定义锁提示和数据库快照隔离配置:
核心配置要点
a. 替换锁模式:PESSIMISTIC_READ → PESSIMISTIC_WRITE
将仓库方法上的@Lock(LockModeType.PESSIMISTIC_READ)改为@Lock(LockModeType.PESSIMISTIC_WRITE)——只有获取更新锁(U锁)才能确保同一行的并发更新互斥,从根源防止静默更新丢失。
b. 自定义方言:生成UPDLOCK + ROWLOCK提示
实现自定义方言,重写FOR UPDATE的锁提示:
public class CustomSQLServer2012Dialect extends SQLServer2012Dialect { @Override public String getForUpdateString(String aliases) { return " WITH (UPDLOCK, ROWLOCK)"; } @Override public String getForUpdateString() { return " WITH (UPDLOCK, ROWLOCK)"; } }
在Spring配置中指定该方言:
spring.jpa.properties.hibernate.dialect=com.yourcompany.persistence.CustomSQLServer2012Dialect
c. 开启SQL Server的READ_COMMITTED_SNAPSHOT选项
在数据库执行以下SQL:
ALTER DATABASE [你的数据库名称] SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;
为什么这套配置能满足所有需求
- 非FOR UPDATE读无阻塞:开启READ_COMMITTED_SNAPSHOT后,普通读操作会读取行的版本化快照,完全不受UPDLOCK行锁的阻塞,符合需求1。
- 插入无阻塞:UPDLOCK仅锁定已存在的行,插入新行的操作不涉及现有行锁,自然不会被阻塞,符合需求2。
- 同一行并发FOR UPDATE互斥:UPDLOCK是排他性的更新锁,同一行只能被一个事务获取,其他事务尝试获取会被阻塞(或根据Hibernate锁超时配置抛出异常),符合需求3。
- 不同行并发FOR UPDATE无阻塞:ROWLOCK提示强制SQL Server使用行级锁,不同行的锁互不干扰,多个事务可同时锁定不同行执行更新,符合需求4。
内容的提问来源于stack exchange,提问作者Dan Angelov
相关产品推荐
相关产品推荐

