乐观锁实现原理笔记
乐观锁(Optimistic Lock)是常用于高并发场景下的并发控制策略。它假设数据不会被频繁冲突,每次操作时先不加锁,操作完成前再验证数据是否被修改。
一、乐观锁简介
1.1 什么是乐观锁?
- 思想:对数据的修改持“乐观态度”,即默认不会发生并发冲突。
- 实现方式:不加锁,而是通过 版本号(version) 或 时间戳(timestamp) 来判断是否存在冲突。
1.2 乐观锁与悲观锁对比
对比项 | 乐观锁 | 悲观锁 |
---|---|---|
锁机制 | 不加数据库锁,通过版本号/时间戳检测冲突 | 加锁控制(如 select ... for update ) |
并发性能 | 高,适合读多写少场景 | 低,适合高冲突/写多场景 |
数据一致性 | 需手动处理冲突 | 通过锁机制自动保障 |
使用方式 | 依赖程序逻辑或框架(如 ORM 乐观锁) | 依赖数据库原生机制 |
二、实现方式详解
2.1 基于版本号实现
Step 1:数据表结构添加 version 字段
CREATE TABLE product (id BIGINT PRIMARY KEY,name VARCHAR(100),stock INT,version INT
);
Step 2:查询数据
SELECT id, stock, version FROM product WHERE id = 1;
Step 3:更新时携带 version 条件
UPDATE product
SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = 3;
- 如果
version = 3
条件不成立,则说明数据已被其他线程修改,更新失败。
Step 4:程序判断返回值
int updated = jdbcTemplate.update(...);
if (updated == 0) {// 更新失败,需要重试或报错
}
2.2 基于时间戳实现
- 原理与版本号类似,使用
last_update_time
字段进行比较。
UPDATE product
SET stock = stock - 1, last_update_time = NOW()
WHERE id = 1 AND last_update_time = '2024-01-01 10:00:00';
2.3 应用于对象模型(Java Bean)
@Data
public class Product {private Long id;private String name;private Integer stock;private Integer version; // 乐观锁字段
}
可集成到 MyBatis-Plus、JPA、Hibernate 等 ORM 框架中。
三、实际开发中应用(MyBatis-Plus 示例)
3.1 配置实体类
@TableName("product")
public class Product {private Long id;private String name;private Integer stock;@Versionprivate Integer version;
}
3.2 使用 updateById
自动带上版本校验
Product product = productMapper.selectById(1L);
product.setStock(product.getStock() - 1);
productMapper.updateById(product); // 内部封装了 version 检查逻辑
如果版本冲突,
updateById
会返回 0 条更新。
四、重试机制与异常处理
在并发场景下,乐观锁失败是常态。可通过如下方式应对:
4.1 编码重试机制
int maxRetries = 5;
while (maxRetries-- > 0) {Product product = productMapper.selectById(1L);product.setStock(product.getStock() - 1);int result = productMapper.updateById(product);if (result == 1) break;
}
4.2 提示用户冲突
在用户敏感场景(如提交订单)中,提示用户“操作冲突,请稍后重试”更合理。
五、乐观锁适用场景
场景 | 是否适合使用乐观锁 |
---|---|
秒杀、抢购 | 是,减少锁竞争 |
数据仓库批量更新 | 否,更新失败率高 |
高并发写操作 | 否,冲突率高 |
读多写少的配置表 | 是,冲突概率小 |
后台修改配置 | 是,保护不被覆盖 |
六、常见问题与误区
问题 | 原因与建议 |
---|---|
更新总是失败 | 并发冲突严重,考虑加悲观锁或队列削峰 |
忘记校验版本号 | 导致乐观锁失效 |
版本字段未加索引 | 可能导致更新效率低 |
乐观锁字段更新遗漏 | 导致版本不自增,逻辑失效 |
幂等更新未处理 | 可结合业务 ID 或唯一请求 ID 去重 |
七、乐观锁与分布式系统
在分布式场景中,乐观锁配合以下方式使用:
- Redis + Lua 脚本原子操作(非关系型乐观锁)
- ZooKeeper 乐观版本控制(基于节点 stat 的 version)
- 数据库层乐观锁 + 分布式 ID 生成器(雪花算法等)
八、总结
关键点 | 内容 |
---|---|
乐观锁核心 | 版本号/时间戳字段 + CAS(Compare-And-Swap)更新 |
实现方式 | SQL 级校验 or ORM 框架自动实现 |
优势 | 无需数据库锁,性能高,易扩展 |
局限 | 冲突率高时更新失败多,需引入重试/补偿逻辑 |
实战建议 | 配合幂等机制、补偿机制、异步队列等策略使用 |