# MyBatis-Plus教程 - 13 悲观锁和乐观锁

在讲解悲观锁和乐观锁之前,先说一下可能遇到的问题。

# 13.1 并发问题

以商品减库存为例,假设有一件 ID 为 1 的商品,库存为 100,事务A事务B 同时购买 10 件库存。

购买时,事务A 先查询商品的库存为 100,在 100 的基础上减10 ,更新库存为 90;事务B 同时查询到库存为 100,也在 100 的基础上减10,更新库存为 90。事务A事务B 先后提交(A先B后),导致最终库存值变为 90,而不是 80,而 事务A 的更新被覆盖,库存减少 10 的操作丢失了。


你可能会想,通过如下的 SQL 操作是否可以解决:

UPDATE tb_prod SET prod_count = prod_count - 10 WHERE id = 1 AND prod_count > 0;
1

prod_count = prod_count - 10 是直接在数据库中执行的,不存在中间读取数据的步骤,因此可以避免在应用层读取库存后再减库存带来的并发问题。

但是数据库的事务隔离级别仍然可能导致并发问题,因为数据库的隔离级别可能设置的不是串行化的,如果数据库的隔离级别设置为串行化的,那么可以解决这个问题,但这种方式可能会导致性能下降。

如果数据库隔离级别不是串行化的,那么两个事务同时执行这条语句的时候,会先读取到 prod_count 的值,那么它们读取到的值将都是100,并判断 prod_count > 0 成立,那么后面再同时执行 UPDATE 语句,都设置 prod_count = 100 - 10,还是出现了问题。

虽然 UPDATE 操作是原子性的,数据库会确保 事务Aprod_count = prod_count - 10 操作不会被其他事务中断,但它不能解决并发事务中的竞争问题,也就是说 事务A事务B 在更新操作之前,都需要读取相同的初始值 prod_count = 100,这部分读取过程并不受原子性保障,因此两个事务都可以基于相同的初始值来进行更新计算。所以出问题了。

怎么解决呢,下面说一下悲观锁和乐观锁。

# 13.2 悲观锁和乐观锁

# 1 悲观锁

悲观锁就是很悲观,总有刁民想害朕,认为在我修改的时候,肯定有别人也在修改,所以在查询数据的时候就会对数据进行加锁,此时别人是无法查询修改数据的,必须等我修改完成,别人才能查询和修改数据。

在使用悲观锁的时候,查询和修改数据是一致的,在查询到的数据的基础上修改不会出问题。但是悲观锁会降低系统的并发性能,如果对数据一致性要求非常高,并且可以接受一定的性能损失,那么可以使用悲观锁。

# 2 乐观锁

乐观锁相对于乐观一些,在查询数据的时候是不会上锁的,在执行更新的时候判断数据是否被修改过,如果没有修改过就修改,如果被修改过了,就返回修改失败。

实现的思路一般是在数据库的表中添加一个 version 的字段,在要修改数据之前查询到 version 字段的值,然后在修改的时候,判断 version 的值是否和查询到的一致,如果一致,那么就可以修改数据,同时将 version 的值 +1 ,如果不一致,说明数据被修改了,则不执行更新。

对应执行的 SQL 大概是:

updateset 字段=新值, version = version + 1 where version = 查询到的verison
1

乐观锁不会影响数据的查询,并发效率高。存在的问题是可能看到的数据不是真实的数据,例如看到有库存,然后购买,购买的时候提示没有库存了。

# 13.3 MyBatis-Plus实现乐观锁

下面看一下在 MyBatis-Plus 中,如何实现乐观锁。

内容未完......