关于SQL Server悲观锁及Updlock、Holdlock、Rowlock的使用疑问
嘿,针对你的两个问题,我来详细拆解一下:
1. 关于(updlock, holdlock, rowlock)的作用及组合影响
咱们逐个拆解这三个锁提示的作用:
UPDLOCK(更新锁):它是SQL Server介于共享锁(S锁)和排他锁(X锁)之间的特殊锁类型。加了这个锁后,其他事务仍能读取对应数据,但无法给这条数据加UPDLOCK或排他锁(也就是没法修改/删除)。核心作用是避免死锁——如果用普通共享锁,多个事务都加S锁后要升级为X锁时会互相等待,而UPDLOCK直接把锁级别提到更新锁,后续修改时能直接升级为X锁,不会产生冲突。HOLDLOCK(持有锁):这个提示等价于让查询运行在**SERIALIZABLE(可串行化)**隔离级别下。默认SQL Server在查询完成后会立即释放共享/更新锁,但HOLDLOCK会让锁一直持有到整个事务提交或回滚,目的是防止幻读,保证事务执行期间不会有其他事务插入、修改或删除符合当前查询条件的数据。ROWLOCK(行级锁):强制SQL Server对匹配的单行数据加锁,而非默认的页锁或表锁。锁粒度越小,并发性能越好,能减少无关操作的锁竞争,避免因锁粒度太大导致不必要的阻塞。
当三个锁提示组合使用时:
你的查询会对匹配行加上更新锁,并且这个锁会一直持有到事务结束(HOLDLOCK),同时强制锁粒度为行级(ROWLOCK)。这种组合的优势是:
- 保证当前事务后续的删除/更新操作能顺利执行,不会被其他事务抢占;
- 避免锁升级到页或表级别,提升系统并发能力;
- 防止事务执行期间出现幻读,保障数据一致性。
2. 为什么不同行的查询会被阻塞?
这个情况看起来反直觉,但大概率是索引缺失导致的锁粒度升级,具体原因可能有这些:
- 查询无合适索引:你的仓库方法通过
tenantId和assetTypeName查询,但实体类的唯一约束是(DATA_TYPE_VALUE, DATA_TYPE_NAMESPACE_INDEX, TENANT_ID, ASSET_TYPE_NAME)——这个索引的前缀不包含tenantId和assetTypeName,SQL Server无法通过它快速定位目标行,只能做全表扫描或大范围页扫描。 - ROWLOCK提示被忽略:当SQL Server做全表/页扫描时,可能会忽略
ROWLOCK提示,转而使用页锁或表锁。此时事务1加的锁覆盖了事务2要查询的行所在的页,导致事务2被阻塞,哪怕它们查的是不同行。 - 锁升级触发:SQL Server默认当一个事务持有超过5000个行级锁时,会自动升级为表锁。如果你的查询扫描了大量行,就会触发锁升级,整个表被锁后其他事务自然被阻塞。
验证与解决方法:
你可以按以下步骤排查和解决:
- 查看查询的执行计划,确认是否做了全表扫描;
- 创建针对
(tenantId, assetTypeName)的非唯一索引(如果这两个字段组合唯一,也可以建唯一索引):CREATE NONCLUSTERED INDEX IX_Mapping_Tenant_AssetType ON mapping (tenant_id, asset_type_name); - 重新测试并发删除请求,此时SQL Server应该能通过索引快速定位单行,加行级锁,不会再出现互相阻塞的情况。
内容的提问来源于stack exchange,提问作者Sefas
相关产品推荐
相关产品推荐

