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
相关产品推荐
相关产品推荐

