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

关于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. 为什么不同行的查询会被阻塞?

这个情况看起来反直觉,但大概率是索引缺失导致的锁粒度升级,具体原因可能有这些:

  1. 查询无合适索引:你的仓库方法通过tenantId和assetTypeName查询,但实体类的唯一约束是(DATA_TYPE_VALUE, DATA_TYPE_NAMESPACE_INDEX, TENANT_ID, ASSET_TYPE_NAME)——这个索引的前缀不包含tenantId和assetTypeName,SQL Server无法通过它快速定位目标行,只能做全表扫描或大范围页扫描。
  2. ROWLOCK提示被忽略:当SQL Server做全表/页扫描时,可能会忽略ROWLOCK提示,转而使用页锁或表锁。此时事务1加的锁覆盖了事务2要查询的行所在的页,导致事务2被阻塞,哪怕它们查的是不同行。
  3. 锁升级触发:SQL Server默认当一个事务持有超过5000个行级锁时,会自动升级为表锁。如果你的查询扫描了大量行,就会触发锁升级,整个表被锁后其他事务自然被阻塞。

验证与解决方法:

你可以按以下步骤排查和解决:

  • 查看查询的执行计划,确认是否做了全表扫描;
  • 创建针对(tenantId, assetTypeName)的非唯一索引(如果这两个字段组合唯一,也可以建唯一索引):
    CREATE NONCLUSTERED INDEX IX_Mapping_Tenant_AssetType ON mapping (tenant_id, asset_type_name);
    
  • 重新测试并发删除请求,此时SQL Server应该能通过索引快速定位单行,加行级锁,不会再出现互相阻塞的情况。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 14:53:12