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

如何提升.NET REST API批量上传速度同时避免SQL唯一键冲突?

解决SQL Server批量插入的速度与冲突问题

为什么提前校验在Azure环境失效?

  • 本地localhost是单实例运行,校验和插入的时间差内不会有其他请求修改数据,但Azure是多实例部署,不同实例的请求可能同时操作同一数据,校验和插入不是原子操作,会出现竞态条件:实例A校验记录不存在,实例B在A插入前先插入了该记录,最终A插入时触发唯一键冲突。

解决方案:无需二选一,兼顾速度与可靠性

下面是几种可行的方案,按推荐程度排序:

方案1:使用SQL Server表值参数(TVP) + 原子性插入语句

通过自定义SQL结合表值参数,实现一次批量插入且跳过冲突行,既保证速度,又避免整组失败。

步骤:

  1. 在SQL Server中创建对应的表值类型:
CREATE TYPE dbo.MyDataItemType AS TABLE (
    Id INT PRIMARY KEY,
    Name NVARCHAR(100) NOT NULL,
    -- 其他字段与你的目标表结构匹配
);
  1. 在.NET代码中构造TVP并执行批量插入:
// 假设你的数据模型是MyDataItem
var batchItems = current200Items; // 当前要插入的200条数据

// 构造表值参数
var tvpParam = new SqlParameter("@BatchItems", SqlDbType.Structured)
{
    TypeName = "dbo.MyDataItemType",
    Value = batchItems.AsEnumerable()
};

// 执行原子性插入,跳过已存在的记录
await _dbContext.Database.ExecuteSqlRawAsync(@"
INSERT INTO YourTargetTable (Id, Name, OtherColumns)
SELECT Id, Name, OtherColumns FROM @BatchItems
WHERE NOT EXISTS (
    SELECT 1 FROM YourTargetTable t 
    WHERE t.Id = @BatchItems.Id -- 替换为你的唯一键字段
)", tvpParam);
  • 优势:原生SQL支持,无需第三方库,插入操作是原子性的,彻底避免竞态问题,速度和批量SaveChanges相当甚至更快。

方案2:使用EF Core批量操作库(如EFCore.BulkExtensions)

第三方库封装了批量操作细节,支持一键忽略冲突或更新冲突行,代码更简洁。

  1. 安装NuGet包:Install-Package EFCore.BulkExtensions
  2. 调用批量插入方法并设置冲突处理:
await _dbContext.BulkInsertAsync(current200Items, options =>
{
    options.IgnoreOnMergeConflict = true; // 忽略冲突行,正常插入其他行
    options.BatchSize = 200; // 保持你测试过的最优批量大小
});
  • 优势:代码简洁,底层用高效的Bulk Copy/TVP实现,速度快,同时内置冲突处理逻辑,无需自己写复杂SQL。

方案3:捕获异常后拆分重试(备选)

如果不想用自定义SQL或第三方库,可以捕获DbUpdateException,解析冲突行后单独处理,再插入剩余行。

try
{
    await _dbContext.AddRange(current200Items);
    await _dbContext.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
    var sqlException = ex.InnerException as SqlException;
    // SQL Server唯一键冲突错误码为2601或2627
    if (sqlException != null && (sqlException.Number == 2601 || sqlException.Number == 2627))
    {
        // 从错误信息中提取冲突的键值(需根据实际错误提示调整解析逻辑)
        var conflictId = ExtractConflictIdFromMsg(sqlException.Message);
        
        // 移除冲突行,重新插入剩余数据
        var validItems = current200Items.Where(item => item.Id != conflictId).ToList();
        if (validItems.Any())
        {
            _dbContext.AddRange(validItems);
            await _dbContext.SaveChangesAsync();
        }
        
        // 记录冲突行错误信息并返回给前端
        // ...
    }
    else
    {
        // 处理其他数据库异常
        throw;
    }
}
  • 注意:解析错误信息的逻辑需要根据你的唯一键字段调整,这个方法相对繁琐,适合特殊场景。

总结

通过原子性的批量插入操作(方案1或2),可以彻底解决竞态条件导致的冲突问题,同时保持批量插入的速度,不需要在速度和可靠性之间做取舍。

内容的提问来源于stack exchange,提问作者John Henckel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 06:15:45