能否为每个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
相关产品推荐
相关产品推荐

