简介: 讲解高并发里面的乐观锁介绍
- 什么是乐观锁
每次去拿数据的时候都认为别人不会修改,更新的时候会判断是别人是否回去更新数据,通过版本来判断,如果数据被修改了就拒绝更新
Java里面大量使用CAS, CAS这个是属于乐观锁,性能较悲观锁有很大的提高
AtomicXXX 等原子类底层就是CAS实现,一定程度比synchonized好,因为后者是悲观锁
小结:悲观锁适合写操作多的场景,乐观锁适合读操作多的场景,乐观锁的吞吐量会比悲观锁多
- 数据库的乐观锁
大多是基于数据版本 (Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通
过为数据库表增加一个 “version” 字段来 实现。 读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据,库表对应记录的当前版本信息进行比对,如果提交的数据 版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据
Step1.MybatisPlus轻松实现乐观锁
CREATE TABLE `banner` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`img` varchar(524) DEFAULT NULL COMMENT '图片',
`url` varchar(524) DEFAULT NULL COMMENT '跳转地址',
`weight` int DEFAULT NULL COMMENT '权重',
`version` int DEFAULT NULL COMMENT '乐观锁版本号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
模拟数据:
BannerDO.java 实体类
package com.lzh.xd_shop.model;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
/**
* @Author:kaiyang.cui
* @Package:com.lzh.xd_shop.model
* @Project:xd_shop
* @name:BannerDO
* @Date:2023/4/1 下午3:47
* @Filename:BannerDO
* @Description:BannerDO
* @Version:1.0
*/
@Data
@TableName("banner") // 表名映射,mybatisplus必须加
public class BannerDO implements Serializable{
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
private String img;
private String url;
private Integer weight;
@Version
private Integer version;
}
MybatisPlusPageConfig.java
分页插件和乐观锁插件
package com.lzh.xd_shop.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author:kaiyang.cui
* @Package:com.lzh.xd_shop.config
* @Project:xd_shop
* @name:MybatisPlusPageConfig
* @Date:2023/4/1 下午9:31
* @Filename:MybatisPlusPageConfig
* @Description:分页插件和乐观锁插件
* @Version:1.0
*/
@Configuration
public class MybatisPlusPageConfig {
/**
* 新的分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
Step2. 乐观锁的使用【mybatisPlus版】
场景1 没有其他人更新banner表中id=7的记录
更新前的数据表:
@Test
@DisplayName("banner-MyBatisPlus乐观所的使用")
public void test21() throws Exception {
// 一般都是先通过id找到对应的记录,得到id和version,但是mybatisplus帮我们做了
BannerDO bannerDO = new BannerDO();
bannerDO.setVersion(1);
bannerDO.setId(7);
bannerDO.setUrl("juejin.cn");
bannerMapper.updateById(bannerDO);
}
可以发现sql语句,UPDATE banner SET url=?, version=? WHERE id=? AND version=?, 每次更新操作都会在where 后面带上version字段,如果versin字段还是为1,说明此时没有其他人修改这条数据,则执行更新成功!
更新后的数据表:可以看到,在其他人没有修改id=7的这一行记录的时候,此时,“我”正在执行UPDATE banner SET url=?, version=? WHERE id=? AND version=?,则更新操作成功,数据表中version的值由1变为2。
场景2: 有人更新id=7的记录时,则修改失败
我们在执行更新操作前,再来看一眼数据表:
@Test
@DisplayName("banner-MyBatisPlus乐观所的使用")
public void test21() throws Exception {
// 一般都是先通过id找到对应的记录,得到id和version,但是mybatisplus帮我们做了
BannerDO bannerDO = new BannerDO();
bannerDO.setVersion(2);
bannerDO.setId(7);
bannerDO.setUrl("juejin.cn");
bannerMapper.updateById(bannerDO);
}
在执行上述测试用例时,我们在更新操作前打一个断点,debug模式运行
紧接着,在数据表中修改id=7的记录,将字段versin的值由2偷偷修改为3,来模拟其他用户修改词条记录。
此时执行下一步刚刚断点的位置。
发现还是3,别人抢先在数据表中更新了id=7的记录,使得“我”的更新操作并没有修改成功,这个例子使用了乐观锁,可以应用于秒杀、发放优惠券业务。