乐观锁

12/13/2023

# 乐观锁

乐观锁通常会使用版本号机制或者 CAS 算法来实现。通常是在建表时,新增一个 version 版本号字段, 假设初始值为 1。当多线程修改某条记录时,会先进行查询操作(包括版本号 version),后续修改数据时, 会判断查询出来的当前版本号是否与数据库中的一致,一致才会进行更新,并且对 version 版本号进行加 1 操作, 否则更新失败。

适用场景:

  • 读多写少(读数据多写数据少)的场景
  • 并发事务导致的更新丢失(第一类更新丢失【回滚覆盖】和第二类更新丢失【更新覆盖】)
  • 库存修改(乐观锁的变形,本质一样【判断条件为 stock > 0 而非 version = curVersion】)

# 配置乐观锁插件

注意:考虑到项目中可能会添加多个插件,需要注意顺序关系,官方推荐顺序如下:

多租户插件 -> 动态表名插件 -> 分页插件 -> 乐观锁插件 -> sql 性能规范插件 -> 防止全表更新与删除插件。

@Configuration
@MapperScan("com.king.mapper")
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 实体类

实体类中,通过@Version指定锁字段


@Data
public class SkuStock implements Serializable {

    private Integer id;
    
    private Integer productId;
    
    private Integer stock;

    @Version
    private Integer version;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

注意事项:

  • @Version 注解仅支持数据类型为:int, Integer, long, Long, Date, Timestamp, LocalDateTime
  • 整数类型下会执行 newVersion = oldVersion + 1
  • newVersion 会回写到 entity 实体类中
  • 仅支持 updateById(id) 与 update(entity, wrapper) 方法
  • 在 update(entity, wrapper) 方法下, wrapper 不能复用!!!