使用C#并行处理SQL Server遇问题:为何无法删除记录?
首先,我一眼就注意到你代码里的一个常见陷阱——循环变量捕获问题,这很可能是导致所有任务看似完成但没删除数据的核心原因,再加上手动管理连接池的一些隐患,咱们一步步拆解:
1. 最可能的元凶:循环变量的引用捕获
你在创建Task的循环里直接引用了lowId、highId和dbConnection,但C#的lambda表达式捕获的是变量的引用,而不是当前迭代的值。当Task真正开始执行时,循环大概率已经跑完了,所有任务都会使用循环最后一次迭代的lowId、highId和连接值——如果这个范围里没有数据,自然就不会删除任何记录,但任务还是会显示执行完成。
修复方法:在循环内创建局部变量
把当前迭代的范围和连接赋值给循环内的局部变量,让lambda捕获这些局部变量的值,确保每个任务拿到独立的参数:
for (int i = 0; i < connectionPool.Count; i++) { // 保存当前迭代的变量值到局部变量 var currentLow = yourLowIdList[i]; // 替换成你实际的id范围列表 var currentHigh = yourHighIdList[i]; var currentConn = connectionPool[i]; Task task = new Task(() => DeleteRows(currentLow, currentHigh, currentConn)); task.Start(); myTaskList.Add(task); }
2. 手动连接池的隐患:没必要且容易出错
SQL Server本身内置了连接池(默认开启),你手动创建List<SqlConnection>的方式不仅多余,还可能导致连接管理混乱(比如重复使用连接、未正确释放等)。建议改用内置连接池,配合using语句自动管理连接生命周期:
// 去掉手动维护的connectionPool,直接在任务里创建连接 for (int i = 0; i < taskCount; i++) { var currentLow = yourLowIdList[i]; var currentHigh = yourHighIdList[i]; Task task = new Task(() => { using (var conn = new SqlConnection(myConnString)) { DeleteRows(currentLow, currentHigh, conn); } }); task.Start(); myTaskList.Add(task); }
3. SQL语句的问题:字符串拼接+缺乏参数化
你用字符串拼接SQL的方式不仅有SQL注入风险,还可能因为类型转换或范围边界错误导致删除范围无效。改用参数化查询能避免这些问题,同时让SQL Server更好地执行计划缓存:
static void DeleteRows(long lowId, long highId, SqlConnection connection) { if (connection.State != ConnectionState.Open) { connection.Open(); } using (var transaction = connection.BeginTransaction("DeleteTransaction")) { try { var cmdText = "DELETE FROM myTable WHERE id > @LowId AND id < @HighId"; using (var cmd = new SqlCommand(cmdText, connection, transaction)) { // 添加参数,避免拼接错误和注入 cmd.Parameters.AddWithValue("@LowId", lowId); cmd.Parameters.AddWithValue("@HighId", highId); cmd.CommandTimeout = 300; // 记录删除行数,方便排查 int rowsDeleted = cmd.ExecuteNonQuery(); Console.WriteLine($"Task deleted {rowsDeleted} rows (id: {lowId} to {highId})"); } transaction.Commit(); } catch (Exception ex) { transaction.Rollback(); // 抛出异常,让上层AggregateException捕获 throw new InvalidOperationException($"Failed to delete range {lowId}-{highId}", ex); } } }
4. 异常处理的盲区:没检查任务的异常状态
你当前的代码只检查了任务的Status,但如果任务内部发生异常却没有抛出,Task会处于Faulted状态但不会触发上层的AggregateException。建议完善异常处理逻辑:
try { Task.WaitAll(myTaskList.ToArray()); } catch (AggregateException e) { Console.WriteLine("=== Task Failures ==="); // 扁平化异常,方便查看每个任务的错误 foreach (var innerEx in e.Flatten().InnerExceptions) { Console.WriteLine($"{innerEx.Message}\n{innerEx.StackTrace}"); } } // 逐个检查任务状态和异常 foreach (Task task in myTaskList) { Console.WriteLine($"\nTask Status: {task.Status}"); if (task.Exception != null) { Console.WriteLine($"Task Exception: {task.Exception.Flatten().InnerException.Message}"); } }
5. 最后验证:确保id范围有效
一定要确认每个任务分配的lowId和highId满足lowId < highId,且这些范围确实包含要删除的记录(比如可以先跑SELECT COUNT(*) FROM myTable WHERE id > X AND id < Y验证数据量)。如果范围本身没有数据,delete自然不会有任何变化。
内容的提问来源于stack exchange,提问作者JakeJ

