Microsoft SQL Server中UPDLOCK能否阻止IF EXISTS查询对应键的插入?
首先直接给你结论:是的,把UPDLOCK和HOLDLOCK一起用,完全能阻止针对同一AccountName的并发插入操作,下面给你拆解背后的逻辑:
先搞懂两个锁的作用
1. 单独用UPDLOCK的局限
UPDLOCK会给查询命中的行加更新锁,这种锁和共享锁兼容,但和其他更新锁、排他锁互斥。不过这里有个关键问题:如果你的查询没找到匹配的行(也就是第一次执行时SuperCustomer还不存在),UPDLOCK没办法锁定“这个不存在的键对应的范围”,这时候多个并发请求可能同时通过IF NOT EXISTS的判断,一起进入插入步骤,最后触发唯一约束的冲突错误。
2. HOLDLOCK是关键补全
HOLDLOCK其实就是把事务隔离级别设为SERIALIZABLE,它会让查询启用键范围锁——不仅锁定已存在的行,还会锁定AccountName = 'SuperCustomer'这个值对应的键范围,相当于在这个位置“占个坑”,其他事务根本没法在这个范围内插入新行。
当第一个事务执行SELECT ... WITH (UPDLOCK, HOLDLOCK)且没找到匹配行时,会在SuperCustomer对应的键范围上锁死,其他并发的相同查询会被阻塞,直到第一个事务完成插入(或者回滚),这样就彻底避免了多个事务同时插入重复数据的情况。
给你提个小修正
你的代码里表名有点不一致:建表用的是[Customer],但查询里写的是[Customers],得改成统一的。另外建议显式加事务,确保锁的生命周期覆盖整个操作,修正后的代码如下:
BEGIN TRANSACTION; IF NOT EXISTS(SELECT [AccountName] FROM [Customer] WITH (UPDLOCK, HOLDLOCK) WHERE [AccountName] = 'SuperCustomer') BEGIN INSERT INTO [Customer] ([AccountName]) VALUES ('SuperCustomer') END COMMIT TRANSACTION;
总结一下
单独的UPDLOCK搞不定“不存在键的并发插入”,但**UPDLOCK + HOLDLOCK的组合**通过键范围锁把目标键的位置彻底锁住,完美保证你这个“不存在则插入”的逻辑在高并发场景下不会出问题,不会触发唯一约束的报错。
内容的提问来源于stack exchange,提问作者Michael J. Gray

