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

使用C#并行处理SQL Server遇问题:为何无法删除记录?

排查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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:07:38