乐观锁与悲观锁
悲观锁: 悲观锁比较适合插入数据,简单粗暴但是性能一般
乐观锁: 比较适合更新数据, 性能好但是成功率低(多个线程同时执行时只有一个可以执行成功),还需要访问数据库造成数据库压力过大
模拟乐观锁实现流程
第一步: 数据库中增加商品表t_product
并插入一条数据
CREATE TABLE t_product(
id BIGINT(20) NOT NULL COMMENT '主键ID',
NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id));
--插入一条数据
INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
第二步: 定义实体类Product
和mapper接口ProductMapper
@Data
public class Product {private Long id;private String name;private Integer price;private Integer version;
}
@Repository
public interface ProductMapper extends BaseMapper<Product> {
}
第三步: 在测试方法中模拟小王小李同时获取到商品价格然后做修改操作
@Test
public void testConcurrentUpdate() {//小李取出的价格100Product p1 = productMapper.selectById(1L);System.out.println("小李取出的价格:" + p1.getPrice());//小王取出的价格100Product p2 = productMapper.selectById(1L);System.out.println("小王取出的价格:" + p2.getPrice());//小李将价格加了50元存入了数据库,此时商品价格150p1.setPrice(p1.getPrice() + 50);int result1 = productMapper.updateById(p1);System.out.println("小李修改结果:" + result1);//小王将商品减了30元存入了数据库,此时商品价格70p2.setPrice(p2.getPrice() - 30);int result2 = productMapper.updateById(p2);System.out.println("小王修改结果:" + result2);//老板查询商品价格发现是70,即小李的操作被小王覆盖了Product p3 = productMapper.selectById(1L);System.out.println("最后的结果:" + p3.getPrice());
}
Mybatis-Plus实现乐观锁
第一步: 数据库商品表t_product
中添加version字段默认值是0
第二步: 在实体类的version属性上添加@Version注解
表示该属性对应的字段是乐观锁版本号字段
@Data
public class Product {private Long id;private String name;private Integer price;@Versionprivate Integer version;
}
第三步: 在config.MybatisPlusConfig
配置类的MybatisPlusInterceptor
插件对象中添加乐观锁插件对象OptimisticLockerInnerInterceptor
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();//添加分页插件对象interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//添加乐观锁插件对象interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;
}
第四步: 在测类方法中测试乐观锁的效果
- 在执行查询语句时同时获取当前记录的版本号
- 在更新记录信息时同时在当前版本号的基础上加1,但是条件是版本号还是之前查询到的那个版本号,否则所有的更新操作都将失败
@Test
public void testConcurrentUpdate() {//小李取出的价格100//SELECT id,`name`,price,`version`FROM product WHERE id = 1;Product p1 = productMapper.selectById(1L);//小王取出的价格100//SELECT id,`name`,price,`version`FROM product WHERE id = 1;Product p2 = productMapper.selectById(1L);//小李将价格加了50元后存入了数据库,此时商品信息的版本号已经变为了1p1.setPrice(p1.getPrice() + 50);//update t_product set name = 外星人笔记本,price = 150,version = 1 where id = 1 and version = 0int result1 = productMapper.updateById(p1);//小王将商品减了30元,但是执行更新操作时发现商品信息的版本号与自己取出版本号不一致所以会更新失败//update t_product set name = 外星人笔记本,price = 70,version = 1 where id = 1 and version = 0p2.setPrice(p2.getPrice() - 30);int result2 = productMapper.updateById(p2);//商品最后的价格150,小王的更新操作失败了Product p3 = productMapper.selectById(1L);System.out.println("最后的结果:" + p3.getPrice());
}
优化: 如果小王更新失败就重新获取商品的版本号,然后再次执行更新的操作
@Test
public void testConcurrentVersionUpdate() {//小李取出的价格100Product p1 = productMapper.selectById(1L);//小王取出的价格100Product p2 = productMapper.selectById(1L);//小李将价格价格50p1.setPrice(p1.getPrice() + 50);productMapper.updateById(p1);//小王将价格减30p2.setPrice(p2.getPrice() - 30);int result = productMapper.updateById(p2);//result等于0表示更新失败,此时需要重新执行查询语句获取商品版本号并再次执行更新操作if(result == 0){p3 = productMapper.selectById(1L);p3.setPrice(p3.getPrice() - 30);//update t_product set name = 外星人笔记本,price = 120,version = 2 where id = 1 and version = 1productMapper.updateById(p3);}//最后商品的价格120Product p4 = productMapper.selectById(1L);System.out.println("老板看价格:" + p4.getPrice());
}