You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

SQL Server 2017是否保证INSERT的数据可被后续SELECT查询返回?及.NET自动化测试偶发失败排查

关于SQL Server 2017 Insert后Select的一致性问题

首先直接给你核心结论:在默认的事务行为和隔离级别下,SQL Server 2017完全能保证InsertSomeData提交的数据会被后续的GetSomeData查询返回

官方依据说明

SQL Server严格遵循ACID事务特性,其中:

  • 持久性(Durability):一旦事务提交,对数据的修改会被永久写入磁盘(默认禁用延迟持久化),不会因为系统或服务波动丢失。
  • 一致性(Consistency):事务完成后,数据库会从一个合法状态过渡到另一个合法状态,所有后续符合隔离级别的查询都能看到提交后的结果。

SQL Server的默认隔离级别是READ COMMITTED,它明确保证:事务只能读取已经成功提交的数据,不会出现脏读(读取未提交的修改)。这意味着当你的InsertSomeData存储过程执行完成(事务自动提交,因为你没有显式开启事务),任何后续使用默认隔离级别的查询(比如GetSomeData)都能看到这条插入的数据。

那为什么会出现1%的随机失败?

既然理论上应该保证一致性,你的随机失败大概率是其他隐藏因素导致的,给你几个排查方向:

1. 确认xUnit的并行执行是否真的被完全禁用

虽然xUnit官方说明同一类中的测试不会并行,但有几个容易忽略的点:

  • 检查测试项目的xunit.runner.json配置文件,确认parallelizeTestCollections是否设为false(默认是true,如果你的测试类属于默认集合,即使同一类串行,也可能和其他测试类并行,但你说所有测试都在同一个静态类,这点可以优先排除,但还是建议确认)。
  • 给测试类添加专属的非并行集合标记,彻底杜绝并行可能:
    [CollectionDefinition("SerialTests", DisableParallelization = true)]
    public class SerialTestCollection : ICollectionFixture<object> { }
    
    [Collection("SerialTests")]
    public static class YourTestClass
    {
        // 你的测试方法
    }
    

2. 验证Insert操作是否真的成功执行

1%的失败率可能是InsertSomeData偶尔执行失败,但你的代码没有捕获验证:

  • 在测试中,获取ExecuteStoredProcAsync的返回值(它会返回受影响的行数),断言插入行数符合预期:
    using(var conn = OpenConnection()) 
    {
        var rowsAffected = await conn.ExecuteStoredProcAsync("InsertSomeData");
        Assert.Equal(1, rowsAffected);
    }
    
  • 检查SQL Server的错误日志,看看有没有偶尔出现的插入失败(比如主键冲突、约束违反,虽然你提前执行了delete,但不排除极端情况下delete未执行完成的可能)。

3. 检查连接池和事务状态

你的代码每次用using(var conn = OpenConnection())获取新连接,连接池可能会复用之前的连接,如果之前的连接有未提交的事务(理论上using会释放连接,连接池会重置状态,但不排除极端bug):

  • OpenConnection()方法中,添加代码确保连接的事务状态是干净的:
    var conn = new SqlConnection(connectionString);
    await conn.OpenAsync();
    if (conn.State == ConnectionState.Open)
    {
        // 确保没有未提交的事务
        await conn.ExecuteAsync("IF @@TRANCOUNT > 0 ROLLBACK");
    }
    return conn;
    

4. 排查容器化SQL Server的资源瓶颈

你的SQL Server部署在Linux容器中,偶尔的资源不足(CPU、内存、磁盘IO)可能导致事务提交的日志刷盘延迟:

  • 给容器增加资源配额(比如CPU核数、内存),看看失败率是否下降。
  • 在Insert之后短暂等待(比如100ms)再执行Select,验证是否是延迟导致的:
    using(var conn = OpenConnection()) await conn.ExecuteStoredProcAsync("InsertSomeData");
    await Task.Delay(100); // 仅用于临时测试验证
    IEnumerable<MyPoco> results = null;
    using(var conn = OpenConnection()) results = await conn.QueryStoredProcAsync<MyPoco>("GetSomeData");
    

是否需要轮询?

如果经过排查确实找不到根本原因,或者确认是极端情况下的延迟问题,添加带超时的轮询是一个可行的临时解决方案:

IEnumerable<MyPoco> results = null;
var maxRetries = 5;
var retryDelay = TimeSpan.FromMilliseconds(100);
var startTime = DateTime.UtcNow;

while (maxRetries-- > 0)
{
    using(var conn = OpenConnection()) 
        results = await conn.QueryStoredProcAsync<MyPoco>("GetSomeData");
    
    if (results?.Any() == true)
        break;
    
    if (DateTime.UtcNow - startTime > TimeSpan.FromSeconds(2))
        break; // 超时退出
    
    await Task.Delay(retryDelay);
}

Assert.NotEmpty(results);

但还是建议优先找到根本原因,轮询只是治标不治本的办法。

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

火山引擎 最新活动