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

C#循环调用存储过程引发死锁问题求助

解决SQL Server死锁:批量标签删除场景的优化方案

问题根源分析

当前死锁主要源于循环单条删除引发的锁竞争:

  • 事务覆盖了更新、批量新增、循环删除全流程,锁持有时间过长;
  • 每次调用uspDeleteTag都会对单条记录加锁,循环执行时多次获取/释放锁,增大了与自身新增逻辑或其他事务形成循环等待的概率;
  • 新增与删除操作同一张表,若操作顺序不一致,极易触发死锁。

具体优化方案

1. 替换循环单删为批量删除

将多次单条删除合并为一次批量操作,减少锁的持有次数与时间:

步骤1:创建批量操作所需的表类型
CREATE TYPE dbo.IntList AS TABLE (Id INT);
步骤2:修改存储过程支持批量删除
CREATE PROCEDURE [dbo].[uspDeleteTagsBatch] 
    (@LabelIds dbo.IntList READONLY)
AS
BEGIN
    SET NOCOUNT ON;
    -- 可选:用ROWLOCK缩小锁粒度,减少锁范围
    DELETE FROM tgphLabel WITH (ROWLOCK)
    OUTPUT DELETED.LabelId, DELETED.LabelTypeId, DELETED.LabelGroupId
    WHERE LabelId IN (SELECT Id FROM @LabelIds);
END
步骤3:调整C#代码,批量执行删除
public async Task UpdateScenario()
{
    try
    {
        await _businessCore.TransactionManager.Begin();

        _dbcontext.MyData.Update(scenario);

        // 先处理删除:收集所有待删LabelId,批量执行
        var labelIdsToDelete = deletedTags.Select(t => t.LabelId).ToList();
        if (labelIdsToDelete.Any())
        {
            var idTable = new DataTable();
            idTable.Columns.Add("Id", typeof(int));
            foreach (var id in labelIdsToDelete)
            {
                idTable.Rows.Add(id);
            }
            await _dbcontext.Database.ExecuteSqlInterpolatedAsync($"exec uspDeleteTagsBatch @LabelIds = {idTable}");
        }

        // 再处理新增:保证所有事务操作顺序统一
        foreach (var tag in newtags)
        {
            tag.LabelGroupId = labelGroup.LabelGroupId;
            var addedLabel = await _dbcontext.AddTags(tag);
            tag.LabelId = addedLabel.LabelId;
            tag.Attributes.ToList().ForEach(z => z.LabelId = addedLabel.LabelId);
        }

        await Validation.SaveChangesWithValidation(_dbcontext);
        await _businessCore.TransactionManager.Commit();
    }
    catch
    {
       await _businessCore.TransactionManager.Rollback();
       throw;
    }
}

2. 统一操作顺序,避免交叉锁

将删除操作放在新增操作之前,确保所有事务对同一张表的操作顺序一致(先删后加),从根源上避免循环等待导致的死锁。

3. 优化数据库隔离级别(可选)

开启READ_COMMITTED_SNAPSHOT隔离级别,让读操作基于快照读取,不阻塞写操作,同时写操作也不会阻塞读操作,大幅降低死锁概率:

ALTER DATABASE YourDatabaseName SET READ_COMMITTED_SNAPSHOT ON;

额外注意事项

  • 若AddTags也是单条调用,建议同步改为批量新增,进一步缩短事务内的操作时间;
  • 若不需要返回删除的记录,可去掉存储过程中的OUTPUT子句,减少资源占用;
  • 通过SQL Server扩展事件或Profiler监控死锁日志,确认死锁的具体资源与竞争方,针对性调整。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 15:57:42