自学内容网 自学内容网

乐观锁与悲观锁

目录

一、乐观锁

1.场景

2.乐观锁与悲观锁

3.模拟修改冲突

4.乐观锁实现流程

5.Mybatis-Plus实现乐观锁

原理

流程

 版本号机制


一、乐观锁

1.场景

一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小 李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太

高,可能会影响销量。又通知小王,你把商品价格降低30元。

此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王  也在操作,取出的商品价格也是100元。小李将价格加50元,并将100+50=150元存入了数据  库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就 完全被小王的覆盖了。

现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1 万多。

2.乐观锁与悲观锁

上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过 了,则重新取出的被修改后的价格,  150元,这样他会将120元存入数据库。

如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证 最终的价格是120元。

3.模拟修改冲突

数据库中增加商品表

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);

添加实体

package com.qcby.mybatisplus.entity;

import lombok.Data;

@Data

public class Product {

private Long id;

private String name;

private Integer price;

private Integer version;

}

添加mapper

public interface ProductMapper extends BaseMapper<Product> {

}

测试

@Test

public void testConcurrentUpdate() {

//1、小李

Product p1 = productMapper.selectById(1L);

System.out.println("小李取出的价格:" + p1.getPrice());

//2、小王

Product p2 = productMapper.selectById(1L);

System.out.println("小王取出的价格:" + p2.getPrice());

//3、小李将价格加了50元,存入了数据库

p1.setPrice(p1.getPrice() + 50);

int result1 = productMapper.updateById(p1);

System.out.println("小李修改结果:" + result1);

//4、小王将商品减了30元,存入了数据库

p2.setPrice(p2.getPrice() - 30);

int result2 = productMapper.updateById(p2);

System.out.println("小王修改结果:" + result2);

//最后的结果

Product p3 = productMapper.selectById(1L);

//价格覆盖,最后的结果:70

System.out.println("最后的结果:" + p3.getPrice());

4.乐观锁实现流程

数据库中添加version字段

取出记录时,获取当前version

SELECT id,`name`,price,`version` FROM product WHERE id=1

更新时, version + 1,如果where语句中的version版本不对,则更新失败

UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND `version`=1

5.Mybatis-Plus实现乐观锁

修改实体类

package com.qcby.mybatisplus.entity;

 

import com.baomidou.mybatisplus.annotation.Version;

import lombok.Data;

@Data

public class Product {

private Long id;

private String name;

private Integer price;

@Version

private Integer version;

}

添加乐观锁插件配置

@Bean

public MybatisPlusInterceptor mybatisPlusInterceptor(){

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

//添加分页插件

interceptor.addInnerInterceptor(new

PaginationInnerInterceptor(DbType.MYSQL));

//添加乐观锁插件

interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor;

} 

直接添加到启动类里面就行,因为启动类本身也是一个配置类

测试修改冲突

小李查询商品信息:

SELECT id,name,price,version FROM t_product WHERE id=?

小王查询商品信息:

SELECT id,name,price,version FROM t_product WHERE id=?

小李修改商品价格,自动将version+1

UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?

Parameters: 外星人笔记本(String), 150(Integer), 1(Integer), 1(Long), 0(Integer)

小王修改商品价格,此时version已更新,条件不成立,修改失败

UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?

Parameters: 外星人笔记本(String), 70(Integer), 1(Integer), 1(Long), 0(Integer)

最终,小王修改失败,查询价格:  150

SELECT id,name,price,version FROM t_product WHERE id=?

优化流程

@Test

public void testConcurrentVersionUpdate() {

//小李取数据

Product p1 = productMapper.selectById(1L);

//小王取数据

Product p2 = productMapper.selectById(1L);

//小李修改 + 50

p1.setPrice(p1.getPrice() + 50);

int result1 = productMapper.updateById(p1);

System.out.println("小李修改的结果:" + result1);

//小王修改 - 30

p2.setPrice(p2.getPrice() - 30);

int result2 = productMapper.updateById(p2);

System.out.println("小王修改的结果:" + result2);

if(result2 == 0){

//失败重试,重新获取version并更新

p2 = productMapper.selectById(1L);

p2.setPrice(p2.getPrice() - 30);

result2 = productMapper.updateById(p2);

}

System.out.println("小王修改重试的结果:" + result2);

//老板看价格

Product p3 = productMapper.selectById(1L);

System.out.println("老板看价格:" + p3.getPrice());

}

小李和小王同时读取数据:他们各自获取了 Product 对象及其版本号。
小李先修改并提交:小李成功更新了数据,并且版本号加1。
小王尝试修改:由于小王的数据版本号已经过期,更新失败(result2 == 0)。
小王重试:小王重新读取最新数据,再次尝试更新,这次更新成功。
通过这种方式,乐观锁确保了并发操作的安全性,同时避免了长时间持有锁带来的性能问题

原理

乐观锁(Optimistic Locking)假设数据在大多数情况下不会发生冲突,因此在读取数据时不加锁,只在提交更新时检查是否有其他事务对同一数据进行了修改。如果检测到冲突,则根据策略进行处理,如重试或回滚。

流程

读取数据:每个事务读取数据时不加锁。
修改数据:事务在本地对数据进行修改。
提交更新:
在提交更新时,检查数据是否被其他事务修改过。
如果没有被修改,则提交成功。
如果已被修改,则提交失败,并根据策略决定是否重试。

 版本号机制

常用的方式是使用版本号字段(version),具体步骤如下:
读取数据:读取记录时同时读取版本号。
修改数据:修改数据时增加版本号。
提交更新:更新时带上旧版本号,只有当数据库中的版本号与旧版本号一致时,才允许更新,并将版本号加1。


原文地址:https://blog.csdn.net/isolusion/article/details/145212350

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!