一、悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞,直到它拿到锁。
数据库行锁,表锁等,读锁,写锁等使用悲观锁
二 、乐观锁
- 总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会加锁,只在更新的时候会判断一下在此期间别人有没有去更新这个数据。
- 乐观锁支持多线程并发,每个线程在不同的时间节点对数据做更新操作,每次更新时候都会判断其- 他线程是否对数据做了更新。
1、version版本号实现
①、version版本号机制
-
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。
-
当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
②、数据库表结构,
注意需要设置默认值为 1 (这个时mybatis-plus默认值,自己实现可以为其他)
③、sql源码
下面有详细测试代码
// 数据库版本 根据你的 id 查询出来的 version
// 执行更新,此时 user.version = 1 ,数据库版本为 1,当前数据库版本为 2 , 2 != 1 更新失败
update set user(name,version) values(user=#{user.name},version=version+1) where id=#{user.id} and version=#{user.version}
④、实体类
⑥、配置乐观锁插件
选用一种即可
// Spring Boot 方式
@Configuration
@MapperScan("按需修改")
public class MybatisPlusConfig {
/**
* 旧版
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
/**
* 新版
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
}
⑦、测试
模拟两个事务,事务一修改数据未提交、事务二执行插入(乐观锁插件本质)
@Test
public void testLock(){
// select name,version from user; version = 1
User user = userMapper.selectById(2L);
user.setName("ye凌");
// select name,version from user; version = 1
User user2 = userMapper.selectById(2L);
user2.setName("椰林阁");
// 执行更新,此时 user2.version = version = 1 ,更新成功 version = 2
// update set user(name,version) values(user=#{user.name},version=version+1) where id=#{user.id} and version=#{user.version}
userMapper.updateById(user2);
// 数据库版本 根据你的 id 查询出来的 version
// 执行更新,此时 user.version = 1 ,数据库版本为 1,当前数据库版本为 2 , 2 != 1 更新失败
// update set user(name,version) values(user=#{user.name},version=version+1) where id=#{user.id} and version=#{user.version}
userMapper.updateById(user);
}
⑧、缺点
循环开销大
我们知道乐观锁在进行写操作的时候会判断是否能够写入成功,如果写入不成功将触发等待 -> 重试机制,这种情况是一个自旋锁,简单来说就是适用于短期内获取不到,进行等待重试的锁,它不适用于长期获取不到锁的情况,另外,自旋循环对于性能开销比较大。
2、CAS 算法
①、原理
-
CAS(compare and swap) 比较并交换,有三个操作数,内存地址V ,预期值B,要替换得到的目标子A。
-
CAS指令执行时,比较内存地址V与预期值B是否相等,若相等则将A赋给B,(不相等则会循环比较直到相等)整个比较赋值操作是一个原子操作。
②、CAS缺点
-
(1)循环时间开销大:当内存地址V与预期值B不相等时会一直循环比较直到相等;
-
(2)只能保证一个共享变量的原子操作;
③、存在问题
只能保证数据一致,不能保证数据未被修改,存在 ABA 问题
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那么我们就能说明它的值没有被其他线程修改过吗?很明显不是,因为在这段时间内它的值可能被改为其他值,然后又被改回A,那CAS操作就会认为它从来没被改过,这个问题就被称为 CAS 操作的“ABA” 问题;