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

.NET Core多线程数据库操作:如何避免死锁与并发问题

.NET Core中多线程操作数据库的并发与死锁规避方案

我需要在.NET Core中实现一个方法,启动3个并行线程完成以下逻辑:

  • 线程1:检查数据库表T1是否满足condition1,满足则向表T添加一条状态为“waiting”的新行
  • 线程2:针对表T2和condition2执行与线程1相同的逻辑
  • 线程3:处理表T中所有“waiting”状态的行

请问规避死锁与并发问题的最优方案是什么?

我的现有代码如下:

public void Run()
{
    ThreadPool.QueueUserWorkItem(Thread1);
    ThreadPool.QueueUserWorkItem(Thread2);
    ThreadPool.QueueUserWorkItem(Thread3);
}

private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1);
public void Thread1(object state)
{
    semaphoreSlim.Wait();
    try
    {
        using (var db = new BDbContext())
        {
            var t1 = db.T1.ToList();
            if (condition1(t1))
            {
                var t = f(t1);
                db.T.Add(t);
                db.SaveChanges();
            }
        }
    }
    catch (Exception ex)
    {
        // log
    }
    finally
    {
        semaphoreSlim.Release();
    }
}

public void Thread2(object state)
{
    semaphoreSlim.Wait();
    try
    {
        using (var db = new BDbContext())
        {
            var t2 = db.T2.ToList();
            if (condition2(t2))
            {
                var t = f(t2);
                db.T.Add(t);
                db.SaveChanges();
            }
        }
    }
    catch (Exception ex)
    {
        // log
    }
    finally
    {
        semaphoreSlim.Release();
    }
}

public void Thread3(object state)
{
    semaphoreSlim.Wait();
    try
    {
        using (var db = new BDbContext())
        {
            var tt = db.T.Where(k => k.IsWaiting);
            //Do something with tt
            db.SaveChanges();
        }
    }
    catch (Exception ex)
    {
        // log
    }
    finally
    {
        semaphoreSlim.Release();
    }
}

现有代码的核心问题

  • 全局SemaphoreSlim把三个线程完全串行化,彻底失去了并行执行的意义,性能极低
  • 锁的粒度太大:把查询T1/T2、插入T、处理T的所有操作都锁起来,阻塞时间过长
  • 没有利用数据库本身的并发控制能力,完全依赖内存锁,可靠性和扩展性差

最优优化方案

1. 缩小锁粒度,只同步共享资源操作

不需要全局锁,只针对表T的修改操作做同步:

  • 线程1/2中,查询T1/T2的操作可以并行执行,不需要加锁;只有当需要向T插入数据时,才进入同步块
  • 线程3中,处理T的waiting行时,同样只在修改T的阶段和插入操作同步

示例代码(针对T操作的专用锁):

// 只针对表T的操作加锁,全局共享
private readonly SemaphoreSlim _tOperationLock = new SemaphoreSlim(1);

public void Thread1(object state)
{
    try
    {
        using (var db = new BDbContext())
        {
            // 查询T1无需锁,并行执行
            var t1 = db.T1.ToList();
            if (condition1(t1))
            {
                var t = f(t1);
                db.T.Add(t);
                
                // 仅在保存修改时加锁
                _tOperationLock.Wait();
                try
                {
                    db.SaveChanges();
                }
                finally
                {
                    _tOperationLock.Release();
                }
            }
        }
    }
    catch (Exception ex)
    {
        // 日志记录
    }
}

2. 利用数据库行级锁避免并发冲突

内存锁只能控制应用内的并发,分布式场景下失效,更可靠的方式是用数据库的行级锁:

  • 线程3处理waiting行时,用带锁的查询锁定目标行,防止被其他线程修改或重复处理(以SQL Server为例):
public void Thread3(object state)
{
    try
    {
        using (var db = new BDbContext())
        {
            // 用UPDLOCK+ROWLOCK锁定要处理的行,避免并发修改
            var tt = db.T.FromSqlRaw("SELECT * FROM T WHERE IsWaiting = 1 WITH (UPDLOCK, ROWLOCK)").ToList();
            
            // 处理逻辑:比如更新状态、关联其他数据等
            foreach (var item in tt)
            {
                item.IsWaiting = false;
                item.ProcessedTime = DateTime.Now;
            }
            
            db.SaveChanges();
        }
    }
    catch (Exception ex)
    {
        // 日志记录
    }
}

3. 改用异步任务模型(.NET Core最佳实践)

替换ThreadPool.QueueUserWorkItem为Task.Run,结合EF Core的异步方法,提升系统吞吐量:

public async Task RunAsync()
{
    // 三个任务并行执行
    await Task.WhenAll(
        Task.Run(Thread1Async),
        Task.Run(Thread2Async),
        Task.Run(Thread3Async)
    );
}

private async Task Thread1Async()
{
    try
    {
        using (var db = new BDbContext())
        {
            var t1 = await db.T1.ToListAsync();
            if (condition1(t1))
            {
                var t = f(t1);
                db.T.Add(t);
                
                await _tOperationLock.WaitAsync();
                try
                {
                    await db.SaveChangesAsync();
                }
                finally
                {
                    _tOperationLock.Release();
                }
            }
        }
    }
    catch (Exception ex)
    {
        // 日志记录
    }
}

4. 死锁规避关键原则

  • 统一资源访问顺序:如果多个线程需要操作多个表,确保所有线程都按相同顺序访问(比如先操作T1/T2,再操作T,不要交叉)
  • 缩短锁持有时间:锁只在必要的数据库修改阶段持有,不要把查询、计算等耗时操作包含在锁内
  • 合理设置事务隔离级别:默认的读提交级别已经能避免大部分脏读问题,不要随意提升到可重复读或串行化,否则会增加死锁概率

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 16:52:52