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

Cosmos DB全量插入但SQL Server仅插入部分记录的事务问题排查

问题背景

系统最初将数据存储在Azure SQL Server中,因表设计限制无法满足促销活动分析需求,故同时将数据存入NoSQL数据库优化查询与分析。为保证一致性,在向Azure SQL Server和Azure Cosmos DB保存数据时使用事务,认为_dbContext.SaveChangesAsync与transaction.CommitAsync均为原子操作,预期所有记录要么全量存储要么全部不存储。根据官方文档描述:"若任一命令失败,事务在释放时会自动回滚",但实际出现异常现象:

  • Azure Cosmos DB成功插入所有数据
  • Azure SQL Server仅插入部分记录

补充信息

预期行为

  • Azure SQL Server与Cosmos DB应存储相同数量的记录

已检查内容

  • 插入前redPoints包含预期数量的元素
  • SQL Server表无唯一约束、触发器或其他会导致静默拒绝的限制
  • 近期更换日志系统,丢失历史日志,增加调试难度
  • 未启用SQL重试策略

使用工具

  • .NET: 6.0
  • Microsoft.EntityFrameworkCore: 7.0.7
  • MongoDB.Driver: 2.22.0

核心问题

  1. 为何Cosmos DB全量插入成功,而SQL Server仅插入部分记录?
  2. SaveChangesAsync或事务处理是否存在潜在问题?
  3. 如何进一步调试该问题?

简化代码

private async Task SaveRedPointsToDb(
    List<RedPoint> redPoints,
    CancellationToken cancellationToken
)
{
    await using var transaction = await _dbContext.Database.BeginTransactionAsync(
        cancellationToken
    );
    try
    {
        // Azure SQL Server
        await _dbContext.RedPoints.AddRangeAsync(redPoints, cancellationToken);
        await _dbContext.SaveChangesAsync(cancellationToken);

        // Azure Cosmos DB
        var redPointDocuments = redPoints.Select(x => new RedPointDocument(x)).ToList();
        await _redPointCollection.InsertManyAsync(redPointDocuments);

        await transaction.CommitAsync(cancellationToken);
    }
    catch (Exception e)
    {
        // Log...
    }
}

问题解答

1. Cosmos全量成功但SQL仅部分插入的原因

你的代码逻辑中SQL的SaveChangesAsync执行在Cosmos插入之前,而实际结果是Cosmos全量成功、SQL部分成功,说明SaveChangesAsync未抛出异常,而是静默完成了部分插入。可能的具体原因:

  • EF Core批量插入逻辑异常:EF Core 7.0.7的AddRangeAsync+SaveChangesAsync默认会生成批量插入SQL,但如果实体映射存在问题(比如部分实体字段值不符合隐式数据类型要求但未触发异常)、或框架本身存在批量插入bug,可能导致部分记录被跳过。
  • SQL Server端的静默处理:即使表无显式约束,若数据库开启了SET ANSI_WARNINGS OFF等设置,可能导致字段截断、数据类型不匹配等问题被静默忽略,仅插入符合要求的记录。
  • ChangeTracker状态异常:AddRangeAsync未将所有redPoints标记为Added状态,导致SaveChangesAsync仅处理了部分实体。

2. SaveChangesAsync或事务处理的潜在问题

你的事务逻辑存在核心缺陷:

  • 事务范围不覆盖Cosmos DB:EF Core的BeginTransactionAsync仅针对当前DbContext关联的SQL Server生效,Cosmos DB的InsertManyAsync是独立操作,不受该事务控制。这种情况下,SQL操作和Cosmos操作完全是两个独立的事务,根本无法保证一致性。
  • SaveChangesAsync的原子性误解:正常情况下,SaveChangesAsync会将所有待插入记录打包成单一批量SQL提交给SQL Server,理论上是原子操作(要么全成功要么全失败)。但如果SQL Server端存在特殊配置(如IGNORE_DUP_KEY)或EF Core生成的SQL存在拆分逻辑,可能打破原子性。
  • 异常处理不严谨:catch块仅做日志记录,未明确回滚事务(虽然await using会自动释放事务并回滚,但如果异常被内层逻辑吞掉,可能导致事务意外提交)。

3. 进一步调试的方法

  1. 开启EF Core详细日志:在DbContext配置中添加日志过滤,记录生成的SQL语句和执行结果:
builder.Logging.AddFilter("Microsoft.EntityFrameworkCore.Database.Command", LogLevel.Information);

通过日志对比批量插入SQL的受影响行数与redPoints的数量,确认问题出在EF Core还是SQL Server端。
2. 直接验证SQL语句:将EF Core日志中的批量插入SQL复制到SSMS执行,观察是否能全量插入,以此隔离是框架问题还是数据库问题。
3. 检查ChangeTracker状态:在AddRangeAsync后、SaveChangesAsync前,打印_dbContext.ChangeTracker.Entries().Count(),确认所有redPoints都被标记为Added;在SaveChangesAsync后,检查所有实体的State是否变为Unchanged。
4. 查看SQL Server错误日志:即使应用日志丢失,SQL Server自身的错误日志可能记录插入时的警告(如字段截断、数据类型不匹配),可在SQL Server Management Studio中查看。
5. 缩小测试范围:先插入小批量数据(如10条),验证是否能全量成功,逐步增加数据量找到触发部分插入的临界点,缩小排查范围。
6. 添加中间校验逻辑:在SaveChangesAsync后立即查询SQL Server中RedPoints的数量,记录到日志,确认此时是否已经是部分插入状态,再执行Cosmos插入。

内容的提问来源于stack exchange,提问作者Ethan Guo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 13:32:09