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




