![7b50aa63a3d6670fa24a3359a00cf1b5.png](https://i-blog.csdnimg.cn/blog_migrate/4716887f563bad1edacf50bd06838378.jpeg)
悲观锁也叫悲观并发控制,它通常基于一种假设,即冲突的操作发生的很频繁。因此使用一种阻塞的机制来减少操作的竞争
乐观锁也叫乐观并发控制,它通常基于一种假设,即冲突的操作发生的不是很频繁,在这种情况下的阻塞的机制未免太重量级了,性能上消耗很大,为什么呢?原因如下:
一个线程阻塞时,以前执行该线程的处理器核心会转而执行另一个线程。以前执行的线程的执行状态必须保存到内存中,并加载新线程的状态。这种将核心从运行一个线程切换到运行另一个线程的操作称为 上下文切换。
除了直接的上下文切换性能成本,新线程一般会使用来自前一个线程的不同数据。内存访问比处理器时钟慢得多,所以现代系统会在处理器核心与主要内存之间使用多层缓存。尽管比主要内存快得多,但缓存的容量也小得多(一般而言,缓存越快,容量越小),所以任何时刻只能在缓存中保存总内存的小部分。发生线程切换且一个核心开始执行一个新线程时,新线程需要的内存数据可能不在缓存中,所以该核心必须等待该数据从主要内存加载。
因此乐观锁通常会避免阻塞,而采用其他方式来避免冲突
以时间戳为基础的乐观并发控制:这种方式会对每一个对象分配一个时间戳。表示最后访问它的事务,因此每个事务在操作过程当中都需要检查时间戳是否正确,举一个简单的例子,一个事务要对一个证书减10,它会拿到这个数据和它的时间戳,并在本地减10,再取对比时间戳,如果前后一样直接改,并把时间戳更改为当前时间,否则,就重试,因此乐观锁通常伴随着,CAS(比较更改)和自旋(重试),而要向保证该种机制正确性,要保证CAS是原子操作。如java unsafe的compareandswapint。
使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
而MVCC我觉得和乐观锁不是一回事,多版本并发控制可以结合基于锁的并发控制来解决写-写冲突,即MVCC+2PL,也可以结合乐观并发控制来解决写-写冲突。每种存储引擎实现MVCC的方式是不同的,例如乐观并发控制,悲观并发控制。
MVCC实现Read Committed和Repeatable Read
Read Committed - 一个事务读取数据时总是读这个数据最近一次被commit的版本
Repeatable Read - 一个事务读取数据时总是读取当前事务开始之前最后一次被commit的版本(所以底层实现时需要比较当前事务和数据被commit的版本号)。