c# 乐观锁和悲观锁在c#中的实现

来源:这里教程网 时间:2026-02-21 17:38:10 作者:

什么是乐观锁和悲观锁,C# 里为什么不能直接“加锁”这两个概念

乐观锁和悲观锁不是 C# 语言内置的语法或关键字,也不是

lock
Monitor
SpinLock
这类同步原语。它们是**并发控制策略**,需要你根据场景,在数据访问逻辑中主动设计实现。C# 提供的是底层工具(如原子操作、事务、版本字段、数据库行锁),而不是开箱即用的“乐观锁类”。直接搜
OptimisticLockAttribute
PessimisticLock
会找不到——因为它们不存在。

用 EF Core 实现乐观锁:靠
ConcurrencyCheck
RowVersion

这是最常见、也最推荐的乐观锁落地方式,尤其在 ORM 场景下。核心是让数据库在

UPDATE
时校验数据是否被别人改过。

1771666149
[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=占用
<p>public bool TryAcquire()
{
return Interlocked.CompareExchange(ref _state, 1, 0) == 0;
}</p><p>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 正确识别为并发令牌。

相关推荐