MySQL MVCC

MySQL事务隔离级别的实现原理

 

回顾

在MySQL的众多存储引擎中,只有InnoDB支持事务,全部这里说的事务隔离级别指的是InnoDB下的事务隔离级别。html

读未提交:一个事务能够读取到另外一个事务未提交的修改。这会带来脏读、幻读、不可重复读问题。(基本没用)mysql

读已提交:一个事务只能读取另外一个事务已经提交的修改。其避免了脏读,但仍然存在不可重复读和幻读问题。sql

可重复读:同一个事务中屡次读取相同的数据返回的结果是同样的。其避免了脏读和不可重复读问题,但幻读依然存在。数据库

串行化:事务串行执行。避免了以上全部问题。并发

以上是SQL-92标准中定义的四种隔离级别。在MySQL中,默认的隔离级别是REPEATABLE-READ(可重复读),而且解决了幻读问题。简单的来讲,mysql的默认隔离级别解决了脏读、幻读、不可重复读问题。post

不可重复读重点在于update和delete,而幻读的重点在于insert。url

在这里,咱们只讨论可重复读。htm

知识储备

MVCC

译注:blog

  MVCC的全称是“多版本并发控制”。这项技术使得InnoDB的事务隔离级别下执行一致性读操做有了保证,换言之,就是为了查询一些正在被另外一个事务更新的行,而且能够看到它们被更新以前的值。这是一个能够用来加强并发性的强大的技术,由于这样的一来的话查询就不用等待另外一个事务释放锁。这项技术在数据库领域并非广泛使用的。一些其它的数据库产品,以及mysql其它的存储引擎并不支持它。索引

 

说明

网上看到大量的文章讲到MVCC都是说给没一行增长两个隐藏的字段分别表示行的建立时间以及过时时间,它们存储的并非时间,而是事务版本号。

事实上,这种说法并不许确,严格的来说,InnoDB会给数据库中的每一行增长三个字段,它们分别是DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID(若是表设置了主键或者惟一索引,row_id则不会分配)。

可是,为了理解的方便,咱们能够这样去理解,索引接下来的讲解中也仍是用这两个字段的方式去理解。

 

增删查改

在InnoDB中,给每行增长两个隐藏字段来实现MVCC,一个用来记录数据行的建立时间,另外一个用来记录行的过时时间(删除时间)。在实际操做中,存储的并非时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。

因而乎,默认的隔离级别(REPEATABLE READ)下,增删查改变成了这样:

  • SELECT
    • 读取建立版本小于或等于当前事务版本号,而且删除版本为空或大于当前事务版本号的记录。这样能够保证在读取以前记录是存在的。
  • INSERT
    • 将当前事务的版本号保存至行的建立版本号
  • UPDATE
    • 新插入一行,并以当前事务的版本号做为新行的建立版本号,同时将原记录行的删除版本号设置为当前事务版本号
  • DELETE
    • 将当前事务的版本号保存至行的删除版本号

 

快照读和当前读

快照读:读取的是快照版本,也就是历史版本

当前读:读取的是最新版本

普通的SELECT就是快照读,而UPDATE、DELETE、INSERT、SELECT ...  LOCK IN SHARE MODE、SELECT ... FOR UPDATE是当前读。

 

一致性非锁定读和锁定读

锁定读

  在一个事务中,标准的SELECT语句是不会加锁,可是有两种状况例外。SELECT ... LOCK IN SHARE MODE 和 SELECT ... FOR UPDATE。

  SELECT ... LOCK IN SHARE MODE

  给记录假设共享锁,这样一来的话,其它事务只能读不能修改,直到当前事务提交

  SELECT ... FOR UPDATE

  给索引记录加锁,这种状况下跟UPDATE的加锁状况是同样的

一致性非锁定读

  consistent read (一致性读),InnoDB用多版原本提供查询数据库在某个时间点的快照。若是隔离级别是REPEATABLE READ,那么在同一个事务中的全部一致性读都读的是事务中第一个这样的读读到的快照;若是是READ COMMITTED,那么一个事务中的每个一致性读都会读到它本身刷新的快照版本。Consistent read(一致性读)是READ COMMITTED和REPEATABLE READ隔离级别下普通SELECT语句默认的模式。一致性读不会给它所访问的表加任何形式的锁,所以其它事务能够同时并发的修改它们。

 

悲观锁和乐观锁

悲观锁,正如它的名字那样,数据库老是认为别人会去修改它所要操做的数据,所以在数据库处理过程当中将数据加锁。其实现依靠数据库底层。

乐观锁,如它的名字那样,老是认为别人不会去修改,只有在提交更新的时候去检查数据的状态。一般是给数据增长一个字段来标识数据的版本。

 

有这样三种锁咱们须要了解

  • Record Locks(记录锁):在索引记录上加锁。
  • Gap Locks(间隙锁):在索引记录之间加锁,或者在第一个索引记录以前加锁,或者在最后一个索引记录以后加锁。
  • Next-Key Locks:在索引记录上加锁,而且在索引记录以前的间隙加锁。它至关因而Record Locks与Gap Locks的一个结合。

假设一个索引包含如下几个值:10,11,13,20。那么这个索引的next-key锁将会覆盖如下区间:

(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)

 

了解了以上概念以后,接下来具体就简单分析下REPEATABLE READ隔离级别是如何实现的

理论分析

之因此说是理论分析,是由于要是实际操做证实的话我也不知道怎么去证实,毕竟做者水平实在有限。

可是,这并不意味着我在此胡说八道,有官方文档为证。

这段话的大体意思是,在默认的隔离级别中,普通的SELECT用的是一致性读不加锁。而对于锁定读、UPDATE和DELETE,则须要加锁,至于加什么锁视状况而定。若是你对一个惟一索引使用了惟一的检索条件,那么只需锁定索引记录便可;若是你没有使用惟一索引做为检索条件,或者用到了索引范围扫描,那么将会使用间隙锁或者next-key锁以此来阻塞其它会话向这个范围内的间隙插入数据。

做者曾经有一个误区,认为按照前面说MVCC下的增删查改的行为就不会出现任何问题,也不会出现不可重复读和幻读。但实际上是大错特错。

举个很简单的例子,假设事务A更新表中id=1的记录,而事务B也更新这条记录,而且B先提交,若是按照前面MVVC说的,事务A读取id=1的快照版本,那么它看不到B所提交的修改,此时若是直接更新的话就会覆盖B以前的修改,这就不对了,可能B和A修改的不是一个字段,可是这样一来,B的修改就丢失了,这是不容许的。

因此,在修改的时候必定不是快照读,而是当前读。

并且,前面也讲过只有普通的SELECT才是快照读,其它诸如UPDATE、删除都是当前读。修改的时候加锁这是必然的,同时为了防止幻读的出现还须要加间隙锁。

  • 一致性读保证了可用重复读
  • 间隙锁防止了幻读

回想一下

一、利用MVCC实现一致性非锁定读,这就有保证在同一个事务中屡次读取相同的数据返回的结果是同样的,解决了不可重复读的问题

二、利用Gap Locks和Next-Key能够阻止其它事务在锁定区间内插入数据,所以解决了幻读问题

综上所述,默认隔离级别的实现依赖于MVCC和锁,再具体一点是一致性读和锁。

 

演示

上面四幅截图对比,能够看到因为id是主键,用id做为检索条件时只锁定那一个索引记录。接下来,看索引范围的例子

这两幅截图,能够看出,因为没有使用惟一索引做为检索条件,致使不光锁定了索引记录,还锁定了索引之间的间隙,应该是是使用了next-key锁。

 

参考 https://dev.mysql.com/doc/refman/5.7/en/innodb-storage-engine.html

转载:https://www.cnblogs.com/cjsblog/p/8365921.html
相关文章
相关标签/搜索