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

能否为每个Web会话(用户)单独实现Lock方法?

解决单用户偏好重复保存的专属锁问题

问题核心

你的代码使用了全局锁prefsLock,导致所有用户的请求都会互斥等待,而实际需要的是仅锁定当前登录用户,避免同一用户的并发请求重复创建偏好记录。

解决方案一:用户专属锁(应用层控制)

用ConcurrentDictionary<int, object>维护每个用户的专属锁对象,确保同一用户的并发请求排队执行,不同用户互不影响。

步骤1:声明全局线程安全字典

在控制器类中添加静态字典存储用户锁:

private static readonly ConcurrentDictionary<int, object> _userLocks = new ConcurrentDictionary<int, object>();

步骤2:修改Action方法

[HttpPost]
public ActionResult Index(string prefs)
{
    int uid = User.Identity.Id();
    // 获取或创建当前用户的专属锁对象,线程安全
    var userLock = _userLocks.GetOrAdd(uid, _ => new object());

    lock (userLock)
    {
        UserPrefs prefsInDb = db.UserPrefs.SingleOrDefault(p => p.UserId == uid);
        if (prefsInDb != null)
        {
            prefsInDb.Preferences = prefs;
            db.Entry(prefsInDb).State = EntityState.Modified;
        }
        else
        {
            UserPrefs up = new UserPrefs()
            {
                Preferences = prefs,
                UserId = uid,
            };
            db.UserPrefs.Add(up);
        }

        db.SaveChanges();
    }

    // 可选优化:用户长时间无操作时清理锁对象,减少内存占用
    // _userLocks.TryRemove(uid, out _);

    return Json(new { });
}

原理说明

ConcurrentDictionary保证线程安全地为每个用户创建唯一的锁对象,同一用户的并发请求会等待锁释放后执行,不同用户的锁对象独立,不会互相阻塞。

解决方案二:数据库唯一约束(底层保障)

从数据库层面阻止重复记录,即使应用层锁失效,数据库也能直接拒绝重复插入,是更可靠的兜底方案。

步骤1:添加唯一约束

通过EF Fluent API或直接修改数据库,给UserPrefs表的UserId字段添加唯一约束:

// 在DbContext的OnModelCreating方法中配置
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<UserPrefs>()
        .HasIndex(u => u.UserId)
        .IsUnique();
}

步骤2:修改代码处理冲突

捕获数据库的唯一约束冲突异常,重新执行更新逻辑:

[HttpPost]
public ActionResult Index(string prefs)
{
    int uid = User.Identity.Id();
    try
    {
        UserPrefs prefsInDb = db.UserPrefs.SingleOrDefault(p => p.UserId == uid);
        if (prefsInDb != null)
        {
            prefsInDb.Preferences = prefs;
            db.Entry(prefsInDb).State = EntityState.Modified;
        }
        else
        {
            UserPrefs up = new UserPrefs()
            {
                Preferences = prefs,
                UserId = uid,
            };
            db.UserPrefs.Add(up);
        }

        db.SaveChanges();
    }
    catch (DbUpdateException ex)
    {
        // 针对SQL Server的唯一约束冲突错误码(2601),其他数据库需调整
        if (ex.InnerException is SqlException sqlEx && sqlEx.Number == 2601)
        {
            // 并发插入冲突,重新查询并更新已有记录
            var existingPrefs = db.UserPrefs.Single(p => p.UserId == uid);
            existingPrefs.Preferences = prefs;
            db.Entry(existingPrefs).State = EntityState.Modified;
            db.SaveChanges();
        }
        else
        {
            throw; // 其他异常正常抛出
        }
    }

    return Json(new { });
}

注意事项

  • 不同数据库的唯一约束冲突错误码不同:MySQL为1062,PostgreSQL为23505,需根据实际使用的数据库调整。
  • 建议同时使用应用层锁和数据库约束,双重保障避免重复记录。

内容的提问来源于stack exchange,提问作者Steije De Brucker

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 04:37:06