请解释乐观锁与悲观锁分别解决什么并发问题,并给出在 C# 中实现这两种并发控制方式的思路,包括各自适用的典型场景。
考察说明
考察对并发控制核心策略的理解及在 C# 中的落地实现能力。
回答思路
- 【回答框架 1】悲观并发控制基于冲突必然发生的假设,在操作数据前先加锁阻塞其他事务,C# 中常用 lock 语句、Monitor、Mutex 或 Semaphore 实现临界区互斥,体现为串行化访问,适合写冲突频繁、并发竞争激烈的场景,缺点是锁竞争会降低吞吐并可能引发死锁。
- 【回答框架 2】乐观并发控制假设冲突极少发生,不加锁而是在更新时通过版本号或时间戳校验数据是否被其他事务修改,若版本不一致则放弃或重试,C# 中可通过 SQL 的 rowversion 字段比对或使用 ConcurrentDictionary 的 TryUpdate 方法,适合读多写少、冲突概率低的场景,能提升并发性能。
- 【回答框架 3】在 C# 中实现乐观控制时,可定义一个包含 Id、Version 和数据的实体,更新前读取版本,执行更新时在 SQL 语句中加入 WHERE Version = @预期值,若影响行数为零则检测到冲突并重试或提示用户;也可用内存中的 CompareExchange 或 ConcurrentDictionary 的原子操作达到类似效果。
- 【回答框架 4】选择策略需评估业务冲突频率与重试代价,悲观锁提供强一致性但牺牲并发度,乐观锁靠应用层处理冲突,若重试成本高或用户交互流程长则不适合。实际项目中可结合使用,例如数据库行级锁用于写关键数据,版本号校验用于缓存更新。
- 【关键点 1】乐观锁不阻塞其他事务,通过版本号或时间戳校验冲突,适合冲突少的场景。
- 【关键点 2】悲观锁在操作前加锁使事务串行,适合写冲突频繁场景,但存在锁竞争和死锁风险。
- 【关键点 3】C# 中悲观锁可用 lock、Monitor、Mutex 或 Semaphore,乐观锁可用 rowversion 条件更新或 ConcurrentDictionary 的 TryUpdate。
- 【易错点 1】不要把乐观锁的范围错误扩大,它不解决所有并发问题,冲突频繁时重试开销反而更大。
- 【易错点 2】使用锁时要注意锁粒度与持有时间,避免嵌套锁导致死锁。
- 【易错点 3】在分布式环境下,单机锁不适用,需引入分布式锁机制,但分布式锁只保证互斥,不自动保证业务幂等。