# 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;
prod_count = prod_count - 10
是直接在数据库中执行的,不存在中间读取数据的步骤,因此可以避免在应用层读取库存后再减库存带来的并发问题。
但是数据库的事务隔离级别仍然可能导致并发问题,因为数据库的隔离级别可能设置的不是串行化的,如果数据库的隔离级别设置为串行化的,那么可以解决这个问题,但这种方式可能会导致性能下降。
如果数据库隔离级别不是串行化的,那么两个事务同时执行这条语句的时候,会先读取到 prod_count
的值,那么它们读取到的值将都是100,并判断 prod_count > 0
成立,那么后面再同时执行 UPDATE 语句,都设置 prod_count = 100 - 10
,还是出现了问题。
虽然 UPDATE
操作是原子性的,数据库会确保 事务A
的 prod_count = prod_count - 10
操作不会被其他事务中断,但它不能解决并发事务中的竞争问题,也就是说 事务A
和 事务B
在更新操作之前,都需要读取相同的初始值 prod_count = 100
,这部分读取过程并不受原子性保障,因此两个事务都可以基于相同的初始值来进行更新计算。所以出问题了。
怎么解决呢,下面说一下悲观锁和乐观锁。
# 13.2 悲观锁和乐观锁
# 1 悲观锁
悲观锁就是很悲观,总有刁民想害朕,认为在我修改的时候,肯定有别人也在修改,所以在查询数据的时候就会对数据进行加锁,此时别人是无法查询和修改数据的,必须等我修改完成,别人才能查询和修改数据。
在使用悲观锁的时候,查询和修改数据是一致的,在查询到的数据的基础上修改不会出问题。但是悲观锁会降低系统的并发性能,如果对数据一致性要求非常高,并且可以接受一定的性能损失,那么可以使用悲观锁。
# 2 乐观锁
乐观锁相对于乐观一些,在查询数据的时候是不会上锁的,在执行更新的时候判断数据是否被修改过,如果没有修改过就修改,如果被修改过了,就返回修改失败。
实现的思路一般是在数据库的表中添加一个 version
的字段,在要修改数据之前查询到 version
字段的值,然后在修改的时候,判断 version
的值是否和查询到的一致,如果一致,那么就可以修改数据,同时将 version 的值 +1
,如果不一致,说明数据被修改了,则不执行更新。
对应执行的 SQL 大概是:
update 表 set 字段=新值, version = version + 1 where version = 查询到的verison
乐观锁不会影响数据的查询,并发效率高。存在的问题是可能看到的数据不是真实的数据,例如看到有库存,然后购买,购买的时候提示没有库存了。
# 13.3 MyBatis-Plus实现乐观锁
下面看一下在 MyBatis-Plus 中,如何实现乐观锁。
← 12-自动填充 14-防全表更新与删除插件 →