悲观并发控制和乐观并发控制
悲观并发控制(Pessimistic Concurrency Control)和乐观并发控制(Optimistic Concurrency Control)的区别、原理和应用场景:
悲观并发控制
flowchart TD A([开始事务]) --> B[尝试获取锁] B --> C{锁是否可用?} C -->|是| D[加锁(排他锁/共享锁)] C -->|否| E[等待锁释放或超时] E --> B D --> F[操作数据(读/写)] F --> G[提交事务] G --> H[释放锁] H --> I([事务结束]) E -->|超时| J[事务回滚] J --> I
flowchart TD subgraph 事务A A1([事务A开始]) --> A2[获取锁1] A2 --> A3[操作数据1] A3 --> A4[尝试获取锁2] A4 --> A5{锁2是否可用?} A5 -->|否| A6[等待锁2释放] A5 -->|是| A7[完成操作并提交] end subgraph 事务B B1([事务B开始]) --> B2[获取锁2] B2 --> B3[操作数据2] B3 --> B4[尝试获取锁1] B4 --> B5{锁1是否可用?} B5 -->|否| B6[等待锁1释放] B5 -->|是| B7[完成操作并提交] end A6 -->|等待事务B释放锁2| B6 B6 -->|等待事务A释放锁1| A6 A6 -.->|超时或检测死锁| A8([事务A回滚]) B6 -.->|超时或检测死锁| B8([事务B回滚])
flowchart TD A([开始事务]) --> B[读取数据及当前版本号] B --> C[业务处理(修改数据)] C --> D{尝试提交} D --> E[检查版本号是否一致] E -->|版本一致| F[提交事务并更新版本号] E -->|版本不一致| G[回滚事务] G --> H{重试次数 < 最大重试次数?} H -->|是| B H -->|否| I[返回失败] F --> J([事务成功结束]) I --> K([事务失败结束])
1. 悲观并发控制
核心思想:
假设多个事务之间容易发生冲突,因此在操作数据前先加锁,阻止其他事务访问,直到当前事务完成。
实现方式:
- 共享锁(读锁) :允许其他事务读取,但不能修改。
- 排他锁(写锁) :禁止其他事务读写。
- 锁的粒度:行锁、表锁、页锁等(例如 MySQL 的
SELECT ... FOR UPDATE
)。
典型场景:
- 高冲突环境(如银行转账、库存扣减)。
- 需要强一致性的场景(如金融系统)。
优点:
- 保证数据一致性,避免脏读、不可重复读等问题。
- 适合冲突频繁的场景。
缺点:
- 锁机制可能引发性能瓶颈(锁竞争、死锁)。
- 事务响应时间较长(等待锁释放)。
2. 乐观并发控制
核心思想:
假设事务之间冲突概率低,允许事务直接操作数据,仅在提交时检测冲突。如果冲突发生,则回滚并重试。
实现方式:
-
版本号(Versioning) :为数据记录添加版本号或时间戳。
-
提交时校验:比较提交时的版本号与最初读取的版本号是否一致。
- 一致:提交成功,更新版本号。
- 不一致:回滚事务,提示重试。
典型场景:
- 低冲突环境(如论坛评论、用户资料编辑)。
- 读多写少、响应速度要求高的场景(如 Web 应用)。
优点:
- 无锁设计,减少资源竞争,性能更高。
- 适合高并发读操作。
缺点:
- 高冲突时频繁回滚和重试,性能下降明显。
- 需要额外的冲突处理逻辑(如重试机制)。
3. 对比总结
维度 | 悲观并发控制 | 乐观并发控制 |
---|---|---|
冲突假设 | 冲突频繁 | 冲突较少 |
锁机制 | 提前加锁 | 无锁,提交时校验 |
性能开销 | 锁管理、死锁检测 | 冲突检测、重试机制 |
适用场景 | 强一致性、高冲突(如转账) | 高并发读、低冲突(如评论系统) |
典型数据库 | MySQL(行锁)、SQL Server | MongoDB(默认乐观锁)、Redis |
4. 如何选择?
- 冲突频率:高冲突选悲观,低冲突选乐观。
- 响应需求:要求低延迟选乐观,强一致性选悲观。
- 重试成本:若重试代价高(如复杂业务逻辑),悲观更可靠。
- 混合使用:部分场景可结合两种策略(例如分段锁+版本号)。