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

.Net WebService API并发调用致SQL重复插入问题求助

解决并发场景下.Net 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:59:34