MySQL 的乐观锁和悲观锁是什么?
在并发环境下,为了避免数据竞争和保证数据一致性,可以使用不同的锁策略。乐观锁和悲观锁是两种常见的并发控制机制,它们在锁定数据时的理念和实现方式上有显著区别。
1. 悲观锁(Pessimistic Lock)
定义
- 悲观锁是一种保守的并发控制策略,假设数据在被访问时会发生并发修改,因此在操作数据之前会将其锁定,确保其他事务无法修改数据。
- 使用悲观锁的事务在操作数据时会 主动加锁,以防止其他事务对数据的读写操作。
实现方式
- 在 MySQL 中,悲观锁通常依赖于数据库的锁机制,例如 行锁 或 表锁。
- 常用语法:
- 使用
SELECT ... FOR UPDATE
或LOCK TABLES
来实现悲观锁。
- 使用
示例
事务 A 和事务 B 需要修改同一条记录:
- 事务 A 执行以下语句:
START TRANSACTION; SELECT * FROM orders WHERE id = 1 FOR UPDATE;
此时,事务 A 加锁,事务 B 无法操作 id = 1
的记录。
- 事务 B 尝试修改相同记录时会被阻塞,直到事务 A 提交或回滚。
适用场景
- 数据冲突较频繁的场景,例如高并发情况下对相同资源的修改操作。
- 在修改之前需要保证数据的强一致性。
2. 乐观锁(Optimistic Lock)
定义
- 乐观锁是一种开放的并发控制策略,假设数据在被操作期间不会发生冲突,因此不在操作前加锁,而是在提交数据时通过 冲突检测机制 检测是否存在数据竞争。
- 如果检测到数据已被其他事务修改,则回滚并重新尝试。
实现方式
- 乐观锁通常通过 版本号 或 时间戳 来实现。
- 常用逻辑:
- 查询数据时,读取记录的版本号或时间戳。
- 修改数据时,检查记录的版本号或时间戳是否与查询时一致。
- 如果一致,则更新记录并增加版本号;否则,说明数据被其他事务修改,需要重试或终止操作。
示例
表结构如下:
CREATE TABLE orders ( id INT PRIMARY KEY, stock INT, version INT );
事务更新逻辑:
- 查询数据:
SELECT stock, version FROM orders WHERE id = 1;
- 更新数据:
UPDATE orders SET stock = stock - 1, version = version + 1 WHERE id = 1 AND version = 当前版本号;
- 检查受影响的行数:
- 如果行数为 0,表示版本号已变化,需要重试或终止操作。
适用场景
- 数据冲突较少的场景,例如读多写少的业务场景。
- 高并发情况下,提高性能,同时保证一定程度的数据一致性。
3. 乐观锁与悲观锁的对比
对比项 | 乐观锁 | 悲观锁 |
---|---|---|
理念 | 假设冲突很少,操作时不加锁,依赖冲突检测。 | 假设冲突频繁,操作时加锁,防止冲突发生。 |
实现方式 | 依赖版本号、时间戳或 CAS(Compare and Swap)。 | 使用数据库的锁机制,例如 FOR UPDATE 。 |
性能 | 无锁操作,性能较高,适合高并发场景。 | 需要加锁,性能较低,适合冲突频繁场景。 |
冲突检测 | 提交时检测是否冲突。 | 通过加锁避免冲突。 |
适用场景 | 读多写少,冲突较少的场景。 | 写多且冲突频繁的场景。 |
总结
- 悲观锁 通过锁机制保证操作安全,适合冲突频繁的场景,但性能较低。
- 乐观锁 依赖版本控制机制来检测冲突,适合冲突较少的高并发场景,性能更优。
- 在实际开发中,可以根据具体业务需求选择合适的锁机制。例如:
- 数据一致性要求极高时使用 悲观锁。
- 数据冲突概率较低时使用 乐观锁。