悲观锁,乐观锁以及MVCC

上文中,咱们探讨了MySQL不一样存储引擎中的各种锁,在这篇文章中咱们将要讨论的是MySQL是如何实现并发控制的。并发问题有三种,分别为:html

  1. 读-读,不存在任何问题
  2. 读-写,有隔离性问题,可能遇到脏读(会读到未提交的数据) ,幻影读等。
  3. 写-写,可能丢失更新

首先咱们先来看一下悲观锁和乐观锁:git

  • 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操做。

悲观锁(Pessimistic Lock)其实是悲观并发控制(Pessimistic Concurrency Control,“PCC”),顾名思义,就是对并发问题持有悲观的态度,认为每次对数据的操做都会引起并发冲突,所以悲观锁每次操做数据的时候都会上锁,以屏蔽一切可能违反数据完整性的操做。github

悲观锁实际上就是运用了上一篇中提到的各种锁来实现并发控制,较为简单,可是开销比较大,并且只支持读-读并发,即对于同一个数据来讲,若是采用悲观锁,那么读-写和写-写并发是不被容许的。数据库

 

  • 乐观锁:假设不会发生并发冲突,只在提交操做时检查是否违反数据完整性。

乐观锁(Optimistic Lock)其实是乐观并发控制(Optimistic Concurrency Control,“OCC”),对并发问题持有乐观的态度,认为不会发生并发冲突,所以不会上锁(因此乐观锁并非一种锁)。可是,在提交更新的时候会判断一下在事务期间是否有其余进程更新了同一块数据。乐观锁解决了写-写冲突的无锁并发控制(注意,这边的无锁并非真正的无锁,而是在执行过程当中不加锁,在检测是否冲突的时候仍是须要对数据进行加锁,可是这边加锁的时间明显少了不少)。乐观锁通常来讲有如下两种实现方式:并发

1.使用数据版本(Version)记录机制实现,这是乐观锁最经常使用的一种实现方式。何谓数据版本?即为数据增长一个版本标识,通常是经过为数据库表增长一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当咱们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,若是数据库表当前版本号与第一次取出来的version值相等,则予以更新,不然认为是过时数据。用下面的一张图来讲明:mvc

如上图所示,若是更新操做顺序执行,则数据的版本(version)依次递增,不会产生冲突。可是若是发生有不一样的业务操做对同一版本的数据进行修改,那么,先提交的操做(图中B)会把数据version更新为2,当A在B以后提交更新时发现数据的version已经被修改了,那么A的更新操做会失败。htm

 

2.使用时间戳(timestamp)。乐观锁定的第二种实现方式和第一种差很少,一样是在须要乐观锁控制的table中增长一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version相似,也是在更新提交的时候检查当前数据库中数据的时间戳和本身更新前取到的时间戳进行对比,若是一致则OK,不然就是版本冲突。blog

 

  • 悲观锁和乐观锁的应用场景

从上文能够看到:进程

1. 当发生并发冲突的几率比较大时,悲观锁更合适,以提升事务的成功率。事务

2. 当发生并发冲突的几率小时(如读多写少),乐观锁更合适,能够提升系统的吞吐量。

 


 

接下来咱们来看看MVCC是什么。

MVCC的意思为多版本并发控制(Multiversion concurrency control),它解决的是读-写并发的问题。MVCC通常来讲也能够当作是一种乐观机制,和间隙锁同样,它能够用来解决幻读的问题,只是间隙锁解决幻读是用使写进程阻塞的方式来进行的,而MVCC是以快照的方式来处理这一问题。不一样数据库版本对MVCC的实现机制不一样,在这边咱们讨论InnoDB是如何进行MVCC的。

InnoDb 会为每一行记录增长两个字段,当前行建立时的版本号删除时的版本号(能够为空),事务在写一条记录时会将其拷贝一份生成这条记录的一个原始拷贝,写操做一样仍是会对原记录加锁,可是读操做会读取未加锁的新记录,这就保证了读写并行。MVCC具体操做以下:

 

SELECT:InnoDB会根据如下两个条件检查每行记录:

1)InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样能够确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。

2)行的删除版本要么未定义,要么大于当前事务版本号。这能够确保事务读取到的行,在事务开始以前未被删除。

INSERT:InnoDB为新插入的每一行保存当前系统版本号做为行版本号。

DELETE:InnoDB为删除的每一行保存当前系统版本号做为行删除标识。

UPDATE:InnoDB为插入一行新记录,保存当前系统版本号做为行版本号,同时保存当系统的版本号为原来的行做为删除标识。

 

InnoDb 经过 MVCC 实现了读写并行,可是在不一样的隔离级别下,读的方式也是有所区别的。首先要特别指出的是,在 read uncommit 隔离级别下,每次都是读取最新版本的数据行,因此不能用 MVCC 的多版本,而 serializable 隔离级别每次读取操做都会为记录加上读锁,也和 MVCC 不兼容,因此只有 RC 和 RR 这两个隔离级别才有 MVCC。

尽管 RR 和 RC 隔离级别都实现了 MVCC 来知足读写并行,可是读的实现方式是不同的:RC 老是读取记录的最新版本,若是该记录被锁住,则读取该记录最新的一次快照,而 RR 是读取该记录事务开始时的那个版本。虽然这两种读取方式不同,可是它们读取的都是快照数据,并不会被写操做阻塞,因此这种读操做称为 快照读(Snapshot Read)。快照读在InnoBD的实现中就是普通不加锁的select语句。与快照读相对应的是当前读,即处理的都是当前的数据,须要加锁,如select  ... lock in share mode,for update以及select,update和delete,在解决当前读的幻读问题时,MySQL使用了间隙锁的机制。

 参考文档:

https://liuzhengyang.github.io/2017/04/18/innodb-mvcc/

http://www.cnblogs.com/chenpingzhao/p/5065316.html

https://riverdba.github.io/2017/04/01/MVCC-theory-study/

相关文章
相关标签/搜索