【MySQL】悲观锁与乐观锁

  • Post author:
  • Post category:mysql


悲观锁和乐观锁

1、什么是悲观锁?

悲观锁是基于一种悲观的态度来防止一切数据冲突,以一种预防的姿态在修改数据之前把数据锁住;

然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作

直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作,一般数据库本身锁的机制都是基于悲观锁的机制实现的;

悲观锁更适用于多写少读的情况。

特点:

可以完全保证数据的独占性和正确性,因为每次请求都会先对数据进行加锁, 然后进行数据操作,最后再解锁,而加锁释放锁的过程会造成消耗,所以性能不高;

2、什么是乐观锁?

乐观锁顾名思义比较乐观,只有在更新数据的时候才会检查这条数据是否被其他线程更新了(这点与悲观锁一样,悲观锁是在读取数据的时候就加锁了)。

如果更新数据时,发现这条数据被其他线程更新了,则此次更新失败。

如果数据未被其他线程更新,则更新成功。

由于乐观锁没有了锁等待,提高了吞吐量,所以乐观锁适合多读少写的场景。

常见的乐观锁实现方式是:版本号version和CAS(compare and swap)。

特点:

乐观锁是一种并发类型的锁,其本身不对数据进行加锁通而是通过业务实现锁的功能,不对数据进行加锁就意味着允许多个请求同时访问数据,同时也省掉了对数据加锁和解锁的过程,这种方式大大的提高了数据操作的性能。

乐观锁实现形式:


版本号


表A 字段

ID Name Version
1 zhangsan 1

1、现在有两个请求同时操作表A,操作1先查询出需要检索的数据为:

ID Name Version
1 zhangsan 1

2、操作2也查询出需要检索的数据为:

ID Name Version
1 zhangsan 1

3、操作1把Name字段数据修改成

lisi

比操作2先提交;

此时提交操作1会把之前查询到的version与现在的数据的version进行比较,版本相同则可以提交,版本不同则视为数据过期:

update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version}

此时数据库变成了如下

ID Name Version
1 lisi 2

那么最后操作2修改了数据来提交的时候是以操作1同样的方式,当然最后肯定不能提交成功,因为他手里的版本号和现在数据库里的版本号不匹配;


CAS


CAS(Compare & Set/Compare & Swap)

CAS操作包含三个操作数——内存位置(V)、预期原值(A)、新值(B)。

如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。 否则,处理器不做任何操作。

无论哪种情况,它都会在CAS指令之前返回该位置的值。

CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

一个 CAS 涉及到以下操作:

假设内存中的原数据V,旧的预期值A,需要修改的新值B

比较 A 与 V 是否相等 如果比较相等,将 B 写入 V

返回操作是否成功

CAS算法原理描述

在对变量进行计算之前(如 ++ 操作),首先读取原变量值,称为 旧的预期值 A

然后在更新之前再获取当前内存中的值,称为 当前内存值 V

如果 A==V 则说明变量从未被其他线程修改过,此时将会写入新值 B

如果 A!=V 则说明变量已经被其他线程修改过,当前线程应当什么也不做。

CAS会面临着ABA问题。

ABA问题描述:

进程P1在共享变量中读到值为A

P1被抢占了,进程P2执行

P2把共享变量里的值从A改成了B,再改回到A,此时被P1抢占。

P1回来看到共享变量里的值没有被改变,于是继续执行。

ABA问题带来的危害:

举例:

小明在提款机,提取了50元,因为提款机问题,有两个线程,同时把余额从100变为50

线程1(提款机):获取当前值100,期望更新为50,

线程2(提款机):获取当前值100,期望更新为50,

线程1成功执行,线程2某种原因block了,这时,某人给小明汇款50

线程3(默认):获取当前值50,期望更新为100,

这时候线程3成功执行,余额变为100,

线程2从Block中恢复,获取到的也是100,compare之后,继续更新余额为50!!!

此时可以看到,实际余额应该为100(100-50+50),但是实际上变为了50(100-50+50-50)这就是ABA问题带来的成功提交。

解决方法: 在变量前面加上版本号,每次变量更新的时候变量的版本号都+1,即A->B->A就变成了1A->2B->3A。

总结

悲观锁:读取时加锁,更新完释放锁,再此过程中会造成其他线程阻塞,导致吞吐量低,适用于多写场景。

乐观锁:不加锁,只有在更新时验证数据是否被其他线程更新,吞吐量较高,适用于多读场景。

参考链接:


浅谈什么是Mysql数据库中的悲观锁和乐观锁?



锁机制及CAS实现原理(C++)



版权声明:本文为ccw_922原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。