前言
Entity Framework (EF) Core 默认主要支持 乐观并发控制(通过并发令牌或 RowVersion),而 悲观并发控制 需要开发者手动实现(通常借助数据库事务和锁机制)
一、悲观并发控制的核心思想
在操作数据前 显式锁定资源(如行锁、表锁),阻止其他事务修改,直到当前事务完成。通常通过数据库的锁机制实现。
二、实现步骤
- 开启事务
使用 BeginTransaction 或 BeginTransactionAsync 开启事务。 - 显式加锁查询
使用 FromSqlRaw 或 FromSqlInterpolated 编写原生 SQL 查询,指定锁机制(如 UPDLOCK)。 - 操作数据
在事务内修改数据,此时锁保持有效。 - 提交事务
提交后释放锁。(trans.Commit();)
1)针对SQL Server数据库
-
示例:
using (var transaction = context.Database.BeginTransaction()) { try { // 查询并锁定目标行(SQL Server 使用 UPDLOCK) long houseId = 1; var house = context.Houses .FromSqlInterpolated($"SELECT * FROM T_Houses WITH (UPDLOCK) WHERE Id = {houseId}") .FirstOrDefault(); if (string.IsNullOrEmpty(house.Owner)) { // 修改数据 house.Owner = paramName; context.SaveChanges(); } // 提交事务,释放锁 transaction.Commit(); } catch (Exception) { transaction.Rollback(); throw; } }
2)针对MySQL数据库
-
示例:
Console.WriteLine("请输入名字"); string name=Console.ReadLine(); using (MyDBContext dbContext=new MyDBContext()) using (var trans=dbContext.Database.BeginTransaction()) { Console.WriteLine($"{DateTime.Now}-准备select for Update"); var house=dbContext.Houses.FromSqlInterpolated($"select * from T_Houses where Id=1 for update").Single(); Console.WriteLine($"{DateTime.Now}-完成select for Update"); if (!string.IsNullOrEmpty(house.Owner)) { if (house.Owner==name) { Console.WriteLine("此房源已被您抢到"); } else { Console.WriteLine($"此房源已被{house.Owner}抢占"); } trans.Commit(); Console.ReadLine(); return; } house.Owner=name; Thread.Sleep(5000); Console.WriteLine($"恭喜{name}抢到了房源{house.Name}"); dbContext.SaveChanges(); trans.Commit(); Console.ReadLine(); }
三、不同数据库的锁语法
- SQL Server: WITH (UPDLOCK) 或 WITH (ROWLOCK)
- PostgreSQL: SELECT … FOR UPDATE
- MySQL: SELECT … FOR UPDATE
四、注意事项
- 事务范围
保持事务尽可能短,避免长时间锁定导致性能问题。 - 死锁风险
确保锁定顺序一致,减少死锁概率。 - 数据库兼容性
不同数据库的锁语法和行为可能不同,需参考具体数据库文档。
五、适用场景
- 高竞争环境(如库存扣减、抢购)
- 需要强制串行化操作的场景
六、与乐观并发的对比
悲观并发 | 乐观并发 | |
---|---|---|
实现方式 | 显式加锁(事务+锁机制) | 版本检查(并发令牌、RowVersion) |
性能 | 高竞争时可能更高效 | 低冲突时更高效 |
复杂度 | 需要手动管理锁和事务 | EF Core 内置支持 |
总结
EF Core 实现悲观并发需通过数据库锁和事务手动控制。虽然灵活,但需谨慎处理锁的范围和事务生命周期,避免性能问题。如果业务允许,优先考虑乐观并发(如 ConcurrencyCheck 特性或 RowVersion)。