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

如何解决Entity Framework中的死锁问题?并行删除代码报错求助

解决EF并行删除引发的死锁问题

为啥会死锁?

你设置了Read Uncommitted隔离级别,但这个级别只影响读取操作的锁行为,删除操作(RemoveRange+SaveChanges)还是会给目标行加排他锁(X锁)。当多个并行事务同时处理不同Code的数据时,如果Code字段没索引,查询会扫全表,导致事务锁一堆无关行;或者不同事务锁资源的顺序乱了,就会触发死锁。

可行的解决办法

1. 给Code字段加索引

先查下dbsHouses表的Code有没有非聚集索引。没有的话,查询Where(d => d.Code == code)会扫全表,锁一大堆行,死锁概率直接拉满。
建索引的SQL(SQL Server为例):

CREATE NONCLUSTERED INDEX IX_dbsHouses_Code ON dbsHouses(Code);

有了索引,查询能直接定位到目标行,锁的范围小了,死锁概率会大幅降低。

2. 别用并行,串行执行

如果业务允许,直接把Parallel.ForEach换成普通foreach:

string[] to = new string[] { "A", "B" };
foreach (var code in to)
{
    DeleteHouses(code);
}

串行的时候,每个事务依次拿锁、释放锁,不会出现互相等锁的情况,直接解决死锁。要是必须并行,接着看下面的方案。

3. 简化事务逻辑,缩短锁持有时间

当前代码里事务包了查询+删除两步,可以优化成只包删除,或者直接用批量SQL删:

方案3.1 直接用原生SQL批量删

跳过EF先查再删的流程,直接让数据库执行删除,锁持有的时间更短:

private static void DeleteHouses(string code)
{
    using var ctx = new BloggingContext();
    string sql = "DELETE FROM dbsHouses WHERE Code = @code";
    ctx.Database.ExecuteSqlRaw(sql, new SqlParameter("@code", code));
}

这种方式不用EF跟踪实体,减少了额外开销,也避免了查询阶段的锁问题。

方案3.2 缩小事务范围

如果非要用EF操作实体,把事务只包在删除和保存的环节:

private static void DeleteHouses(string code)
{
    using var ctx = new BloggingContext();
    // 查询放事务外,用AsNoTracking避免EF跟踪实体
    var todel = ctx.dbsHouses.AsNoTracking().Where(d => d.Code == code).ToList();
    if (todel.Any())
    {
        using var trans = ctx.Database.BeginTransaction();
        ctx.dbsHouses.RemoveRange(todel);
        ctx.SaveChanges();
        trans.Commit();
    }
}

4. 统一锁获取顺序(必须并行时用)

如果业务必须并行,把要处理的code排序后再执行并行:

string[] to = new string[] { "A", "B" }.OrderBy(c => c).ToArray();
Parallel.ForEach(to, code =>
{
    DeleteHouses(code);
});

统一的顺序能避免不同事务交叉锁资源,减少死锁可能。

5. 加死锁重试机制

作为兜底,遇到死锁自动重试:

private static void DeleteHousesWithRetry(string code, int maxRetries = 3)
{
    int retryCount = 0;
    while (retryCount < maxRetries)
    {
        try
        {
            using var ctx = new BloggingContext();
            string sql = "DELETE FROM dbsHouses WHERE Code = @code";
            ctx.Database.ExecuteSqlRaw(sql, new SqlParameter("@code", code));
            return;
        }
        catch (SqlException ex)
        {
            // SQL Server死锁的错误码是1205
            if (ex.Number == 1205)
            {
                retryCount++;
                // 每次重试等一会儿,避免立刻又竞争
                Thread.Sleep(100 * retryCount);
            }
            else
            {
                throw;
            }
        }
    }
    throw new InvalidOperationException($"重试{maxRetries}次后还是删不掉");
}

这样就算碰到死锁,也能自动重试,保证操作最终完成。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 17:25:37