乐观锁和悲观锁是并发控制策略而非C#内置语法,需手动设计实现;C#提供原子操作、事务等底层工具,EF Core通过RowVersion+ConcurrencyCheck实现乐观锁,数据库SELECT FOR UPDATE或UPDLOCK实现悲观锁。

什么是乐观锁和悲观锁,C# 里为什么不能直接“加锁”这两个概念
乐观锁和悲观锁不是 C# 语言内置的语法或关键字,也不是 lock、Monitor 或 SpinLock 这类同步原语。它们是**并发控制策略**,需要你根据场景,在数据访问逻辑中主动设计实现。C# 提供的是底层工具(如原子操作、事务、版本字段、数据库行锁),而不是开箱即用的“乐观锁类”。直接搜 OptimisticLockAttribute 或 PessimisticLock 会找不到——因为它们不存在。
用 EF Core 实现乐观锁:靠 ConcurrencyCheck 和 RowVersion
这是最常见、也最推荐的乐观锁落地方式,尤其在 ORM 场景下。核心是让数据库在 UPDATE 时校验数据是否被别人改过。
-
[Timestamp]或[ConcurrencyCheck]特性标记实体属性(通常是byte[] RowVersion或int Version) - EF Core 会自动把该字段加入
WHERE条件,例如:UPDATE Products SET Name = @p0, RowVersion = @p1 WHERE Id = @p2 AND RowVersion = @p3
- 执行后检查
DbContext.SaveChanges()返回值:如果返回0,说明 WHERE 不匹配 → 数据已被修改 → 抛出DbUpdateConcurrencyException - 捕获异常后可重试(重新加载再提交)、提示用户或合并变更
手动实现乐观锁:用 Interlocked.CompareExchange 处理整数状态
适用于轻量级共享状态(如计数器、开关标志),不涉及数据库。本质是 CPU 级的 CAS(Compare-And-Swap)操作。
private int _state = 0; // 0=空闲, 1=占用public bool TryAcquire() { return Interlocked.CompareExchange(ref _state, 1, 0) == 0; }
public void Release() { Interlocked.Exchange(ref _state, 0); }
注意:Interlocked 只保证单个字段的原子读-改-写,不能保护多字段业务逻辑(比如“扣库存+生成订单”必须用事务或更高层协调)。
悲观锁在 C# 中怎么体现?其实靠的是数据库或外部资源的独占机制
C# 代码本身不“持有悲观锁”,而是通过调用数据库的 SELECT ... FOR UPDATE(MySQL/PostgreSQL)或 WITH (UPDLOCK)(SQL Server)来让数据库行加锁,阻塞其他事务读写。EF Core 不直接支持原生写法,需用 FromSqlRaw 或 ADO.NET:
- 使用
SqlConnection.BeginTransaction(IsolationLevel.RepeatableRead)或Serializable提升隔离级别,间接增强锁力度 - 在事务中执行带锁查询:
SELECT Id, Stock FROM Products WHERE Id = 123 WITH (UPDLOCK, ROWLOCK)
- 锁持续到事务结束(
Commit或Rollback),期间其他事务对该行的 UPDATE/SELECT FOR UPDATE 会被阻塞 - 滥用会导致死锁、响应延迟——必须严格控制事务粒度和时长
真正容易被忽略的是:乐观锁失败后重试逻辑是否幂等;悲观锁的事务范围是否意外包含 UI 等待或远程调用;以及 RowVersion 字段在迁移中是否被 EF Core 正确识别为并发令牌。










