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

如何解决Web应用并行请求下重复创建Foo表同名条目的问题?

解决并行请求下创建唯一名称Foo条目的冲突问题

当两个并行请求同时执行「检查Foo条目是否存在→不存在则创建」的逻辑时,会因为竞态条件导致重复创建同名Foo,违反系统唯一性要求。以下是几种靠谱的解决方法:

1. 数据库层面添加唯一约束(核心保障)

这是最底层、最可靠的防线——直接给Foo表的Name字段添加唯一索引或约束。不管应用层逻辑有没有漏洞,数据库都会拦截重复插入的请求。

应用层需要做的是捕获唯一键冲突异常,然后重新查询已存在的Foo条目,继续后续逻辑。修改后的伪代码如下:

Process(Request request) 
{
    // 耗时约3000ms的业务逻辑
    
    var foo = context.Foos.SingleOrDefault(f => f.Name == request.Name);

    if (foo == null)
    {
        try
        {
            foo = new Foo { Name = request.Name };
            context.Foos.Add(foo);
            context.SaveChanges();
        }
        catch (DbUpdateException ex) when (IsUniqueConstraintViolation(ex))
        {
            // 捕获唯一键冲突,重新拉取已创建的Foo
            foo = context.Foos.Single(f => f.Name == request.Name);
        }
    }
    
    car.Foo = foo;
    context.Cars.Add(car);
    context.SaveChanges();
}

其中IsUniqueConstraintViolation是自定义方法,需要根据你使用的数据库(SQL Server/MySQL/PostgreSQL)判断异常是否为唯一键冲突(比如检查SQL错误码)。

2. 用数据库原子操作消除竞态

把「先查询再插入」的两步操作改成数据库端的原子操作,从根源避免竞态条件。不同数据库有对应的语法:

  • MySQL:INSERT ... ON DUPLICATE KEY UPDATE
  • SQL Server:MERGE
  • PostgreSQL:INSERT ... ON CONFLICT DO NOTHING

用EF执行原生SQL的示例:

// 原子操作:插入新Foo或获取已存在的Foo的ID
var fooId = context.Database.ExecuteSqlInterpolated(
    @"INSERT INTO Foos (Name) 
      VALUES ({request.Name}) 
      ON DUPLICATE KEY UPDATE Id = Id; 
      SELECT Id FROM Foos WHERE Name = {request.Name}"
);
foo = context.Foos.Find(fooId);

这种方式不需要先查询,直接让数据库处理原子性,效率更高。

3. 多实例场景用分布式锁

如果你的Web应用是多实例部署,应用层的内存锁(比如C#的lock)无法跨实例生效,这时候需要用分布式锁(基于Redis、ZooKeeper等实现)。

在执行「检查+创建」逻辑前,先获取以request.Name为标识的分布式锁,只有拿到锁的请求才能执行操作,其他请求等待锁释放后再重新查询。伪代码示例:

Process(Request request) 
{
    // 耗时约3000ms的业务逻辑
    
    using (var distributedLock = await DistributedLockProvider.AcquireLockAsync($"foo_{request.Name}"))
    {
        var foo = context.Foos.SingleOrDefault(f => f.Name == request.Name);

        if (foo == null)
        {
            foo = new Foo { Name = request.Name };
            context.Foos.Add(foo);
            context.SaveChanges();
        }
    }
    
    car.Foo = foo;
    context.Cars.Add(car);
    context.SaveChanges();
}

注意要给分布式锁设置合理的超时时间,避免死锁,同时确保锁能被正确释放(比如用using自动释放)。

总结

  • 优先用数据库唯一约束+异常捕获,这是最稳妥的方案,成本最低;
  • 想从根源消除竞态,就用数据库原子操作;
  • 多实例部署时,再搭配分布式锁进一步降低冲突概率。

内容的提问来源于stack exchange,提问作者Ľuboš Pilka

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 04:25:37