文章目录
- CAS - Compare And Swap
- 业务改造
- 1. 表结构新增version列
- 2. 修改代码
- 3. 测试
- 问题
- 1. 高并发情况下,性能极低
- 2. ABA问题
- 3. 读写分离情况下导致乐观锁不可靠
CAS - Compare And Swap
先比较再交换,一般通过时间戳或者version版本号。
举例:先查询后更新,在数据更新前,先比较数据库中的版本号是否与之前查询的时候一致,若一致则更新,不一致则重新尝试业务。
业务改造
1. 表结构新增version列
2. 修改代码
Stock
@TableName("db_stock")
@Data
public class Stock {
private Long id;
private String productCode;
private String warehouse;
private Integer count;
private Integer version;
}
StockService
@Service
public class StockService {
@Autowired
private StockMapper stockMapper;
/**
* 乐观锁
*/
// @Transactional 将事务注释掉,因为update操作mysql会自动添加事务,降低了事务的粒度,防止事务超时报错
public void deduct() {
// 1。 查询库存
List<Stock> list = stockMapper.selectList(new QueryWrapper<Stock>().eq("product_code", "1001"));
// 2。 判断条件是否满足
if (!CollectionUtils.isEmpty(list)) {
// 假设就拿第一个北京仓的
Stock stock = list.get(0);
if (stock != null && stock.getCount() > 0) {
// 3。 更新库存
Integer version = stock.getVersion();
;
stock.setCount(stock.getCount() - 1);
stock.setVersion(version + 1);
if (stockMapper.update(stock, new UpdateWrapper<Stock>().eq("id", stock.getId()).eq("version", version)) == 0) {
// 如果没有更新成功,则重试
try {
Thread.sleep(20); // 等待20ms。防止太多重试,导致栈溢出
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.deduct(); // 递归调用,重试
}
}
}
}
}
注意两个细节:
- Transactional 事务注解注释掉
update操作mysql会自动加锁,降低了事务粒度,防止事务超时报错- Thread.sleep(20); 失败后睡眠20ms
防止太多重试,导致栈溢出
3. 测试
吞吐量小的可怜,只有7
数据库成功清0,业务没有问题。
问题
1. 高并发情况下,性能极低
会有很多请求重复的去尝试,随着请求越来越多,吞吐量越来越差。
2. ABA问题
A -> B -> A,你之前查询的时候拿到的是A, 然后更新前查询还是A,你以为它一直没有修改,其实它偷偷改了多少次你都不知道。
3. 读写分离情况下导致乐观锁不可靠
读写分离下,读写是分别的库,两个库的数据更新有延迟,拿到的一直都只会是旧数据。