SQL Server并行调用存储过程:求避免插入重复复合键示例
SQL Server 并行插入场景下的唯一键存储过程实现
针对你需要的“先查后插/存在返回ID、不存在插入返回ID”且保证并行下复合键唯一的需求,以下是两种可靠的实现方案,结合SQL Server的锁机制避免竞态问题:
方案一:加锁提示的先查后插
这种方式通过锁提示明确锁定查询范围,避免多个并行会话同时执行“查-插”操作导致重复:
CREATE PROCEDURE dbo.UpsertUniqueRecord @Col1 VARCHAR(50), @Col2 INT, @Col3 DATETIME, @RecordID INT OUTPUT AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; -- 用UPDLOCK+HOLDLOCK锁定符合条件的键范围,阻止其他会话的修改操作 SELECT @RecordID = RecordID FROM dbo.YourTableName WITH (UPDLOCK, HOLDLOCK) WHERE Col1 = @Col1 AND Col2 = @Col2 AND Col3 = @Col3; -- 找到记录直接返回,否则插入新记录 IF @RecordID IS NOT NULL RETURN; INSERT INTO dbo.YourTableName (Col1, Col2, Col3) VALUES (@Col1, @Col2, @Col3); SET @RecordID = SCOPE_IDENTITY(); END
关键说明:
UPDLOCK:为查询到的行(或键范围)加更新锁,其他会话可以读取但无法加更新锁或修改,避免同时插入HOLDLOCK:将锁持有到事务结束,确保从查询到插入的整个过程中,目标键范围的状态不会被其他会话修改SET XACT_ABORT ON:一旦执行出错自动回滚事务,防止锁残留导致死锁
方案二:MERGE语句实现原子操作
SQL Server的MERGE语句支持原子性的匹配/插入操作,代码更简洁,同样能避免竞态:
CREATE PROCEDURE dbo.UpsertUniqueRecord_Merge @Col1 VARCHAR(50), @Col2 INT, @Col3 DATETIME, @RecordID INT OUTPUT AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; MERGE INTO dbo.YourTableName AS Target USING (VALUES (@Col1, @Col2, @Col3)) AS Source (Col1, Col2, Col3) ON Target.Col1 = Source.Col1 AND Target.Col2 = Source.Col2 AND Target.Col3 = Source.Col3 WHEN MATCHED THEN -- 匹配时直接赋值ID UPDATE SET @RecordID = Target.RecordID WHEN NOT MATCHED THEN -- 不匹配时插入并返回新ID INSERT (Col1, Col2, Col3) VALUES (Source.Col1, Source.Col2, Source.Col3) OUTPUT inserted.RecordID INTO @RecordID; END
额外注意事项
- 确保你为
Col1, Col2, Col3创建了唯一非聚集索引,这是防止重复的最终保障,即使锁机制出现极端情况,唯一索引会直接抛出冲突错误阻止重复插入 - Spring Boot端调用时,使用JdbcTemplate或MyBatis的存储过程调用方式,保证每个请求的会话独立性
- 并行插入时控制并发度,过度并发会加剧SQL Server的锁竞争,反而降低插入效率
内容的提问来源于stack exchange,提问作者zamek 42
相关产品推荐
相关产品推荐

