.Net WebService API并发调用致SQL重复插入问题求助
你的问题典型是并发竞态条件导致的:两个请求在几乎同一时间执行SELECT Count(*)检查,都判定记录不存在,接着都执行插入操作,最终违反了Customer+Date的唯一组合约束。下面从数据库和API两个层面给出可靠的解决方案:
一、数据库层面(最核心的一致性保障)
数据库是数据一致性的最后防线,必须在这里做强制约束,再配合存储过程的原子性优化:
1. 给CustomerAccount + ServiceDate添加唯一约束
这是最根本的手段,不管上层代码怎么并发,数据库都会直接阻止重复插入:
ALTER TABLE Records ADD CONSTRAINT UC_Records_Customer_ServiceDate UNIQUE NONCLUSTERED (CustomerAccount, ServiceDate);
添加后,当重复插入时数据库会抛出违反唯一约束的异常,你可以在API或存储过程里捕获这个异常,转为更新操作或者返回友好提示。
2. 用MERGE语句替代先查后改的逻辑
MERGE是原子性操作,能在同一个语句里完成检查、插入、更新,彻底避免竞态条件:
MERGE Records WITH (HOLDLOCK) AS target USING (SELECT @CustomerAccount AS CustomerAccount, @ServiceDate AS ServiceDate) AS source ON target.CustomerAccount = source.CustomerAccount AND target.ServiceDate = source.ServiceDate WHEN MATCHED THEN UPDATE SET -- 填写需要更新的字段 Field1 = @Field1, Field2 = @Field2 WHEN NOT MATCHED THEN INSERT (CustomerAccount, ServiceDate, Field1, Field2) VALUES (@CustomerAccount, @ServiceDate, @Field1, @Field2);
WITH (HOLDLOCK)会在执行期间锁定相关数据范围,避免其他事务插入符合条件的记录,从根源上消除并发冲突。
3. 改进检查逻辑:用表提示避免幻读
如果不想用MERGE,可以修改你的检查语句,加上UPDLOCK + HOLDLOCK表提示来锁定查询范围:
-- 替换原来的Count(*)检查 SELECT @RecordExist = 1 FROM Records WITH (UPDLOCK, HOLDLOCK) WHERE ServiceDate = @Date AND CustomerAccount = @CustomerAccount IF @RecordExist = 1 BEGIN -- 执行更新逻辑 END ELSE BEGIN -- 执行插入逻辑 END
UPDLOCK会获取更新锁,阻止其他事务对同一记录加更新锁;HOLDLOCK会保持锁直到事务结束,避免幻读(也就是其他事务在你检查后插入新记录)。
二、API层面(辅助优化,减少并发冲突概率)
1. 实现请求幂等性
让客户端每次请求携带一个唯一的RequestID(比如UUID),API在处理前先检查这个RequestID是否已经处理过(可以存在数据库日志表或Redis中):
- 如果已经处理过,直接返回之前的处理结果;
- 如果没处理过,再执行存储过程。
2. 分布式锁控制并发
如果你的API是多实例部署,可以用Redis或SQL Server应用锁来控制同一CustomerAccount + ServiceDate的并发请求:
// 伪代码:Redis锁示例 var lockKey = $"Lock_Records_{CustomerAccount}_{ServiceDate:yyyyMMdd}"; using (var redisLock = await _redisClient.LockAsync(lockKey, TimeSpan.FromSeconds(10))) { if (redisLock.IsAcquired) { // 调用存储过程执行插入/更新 } else { // 返回"请求处理中"或等待重试 } }
这样同一时间只有一个请求能处理该组合的记录,避免并发冲突。
3. 客户端防抖优化
既然是每5分钟自动更新的场景,可以让客户端在发送请求前做防抖:比如如果5分钟内已经发送过同一CustomerAccount的更新请求,就跳过或延迟发送,减少不必要的并发请求。
总结
优先保证数据库层面的唯一约束 + 原子性操作,这是最可靠的一致性保障;再配合API层面的幂等性或分布式锁,进一步减少冲突概率。这样即使毫秒级的并发请求,也不会出现重复插入的问题。
内容的提问来源于stack exchange,提问作者Venki

