前言

本篇文章首先会对数据库事务的几个根底概念进行阐明,次要是事务ACID模型并发事务带来的问题事务隔离级别。而后在此基础上,会对MySQLInnoDB引擎中的一致性非锁定读取(Consistent Nonlocking Reads进行较为深刻的演示和解析,次要波及MVCC机制,undo log快照

参考

  • 《深入浅出MySQL
  • MySQL官网文档

注释

一. 事务和事务ACID模型

1. 事务概念

事务概念如下。

事务是由一组SQL语句组成的逻辑处理单元。

即能够将事务了解为一系列的对数据库的操作汇合,这些操作要么全副失效,要么全副不失效。

2. ACID模型

事务的ACID模型如下表所示。

ACID属性项解释
原子性(Atomicity事务是一个原子操作单元,其对数据的批改,要么全都执行,要么全都不执行。
一致性(Consistent在事务开始和实现时,数据都必须保持一致状态。即所有相干的数据规定都必须利用于事务的批改,以保持数据的完整性;事务完结时,所有的外部数据结构(如B树索引或双向链表)也都必须是正确的。
隔离性(Isolation数据库系统提供肯定的隔离机制,保障事务在不受内部并发操作影响的“独立”环境执行。即事务处理过程中的中间状态对其它事务是不可见的。
持久性(Durable事务实现之后,事务对于数据的批改是永久性的,即便呈现系统故障也可能放弃。

二. 并发事务带来的问题

如果事务之间严格依照串行的形式执行,不会呈现并发问题,然而会极大升高对数据库资源的利用率。因而为了减少数据库资源的利用率,进步数据库系统的事务吞吐量,通常事务之间是并发执行的,由此也引入了并发事务带来的问题,如下表所示。

并发事务带来的问题解释
脏读(Dirty Reads一个事务正在对一条记录做批改,在这个事务实现并提交前,这条记录的数据就处于不统一状态,这时,第二个事务来读取同一条记录,如果不加管制,第二个事务会读取这条处于不统一状态的记录,即读取到脏数据,称为脏读
不可反复读(Non-Repeatable Reads一个事务在读取某些数据后的某个工夫,再次读取以前读过的数据,却发现其读出的数据曾经产生了扭转或某些记录曾经被删除,这种景象称为不可反复读
幻读(Phantom Reads一个事务按雷同的查问条件从新读取以前检索过的数据,却发现其它事务插入了满足其查问条件的新数据,这种景象称为幻读

三. 事务隔离级别

为了解决因为并发事务带来的脏读不可反复读幻读的问题,须要借助数据库提供肯定的事务隔离机制,通常有基于乐观锁的加锁机制和基于无锁的多版本并发管制(MultiVersion Concurrency Control, MVCC。事务隔离的本质就是使事务在肯定水平上串行化执行,事务隔离得越严格,串行化的水平就越高,然而相应的数据库的并发能力就越低,为了解决事务的隔离并发的矛盾,引入了事务隔离级别这一概念,不同的隔离级别会导致不同的事务并发问题,每种隔离级别的形容如下表所示。

事务隔离级别形容
读未提交(Read uncommitted,RU事务能够感知到其它未提交事务对数据库所做的变更。
读已提交(Read committed,RC事务无奈感知到其它未提交事务对数据库所做的变更。
可反复度(Repeatable read,RR事务在执行过程中,只能看到事务启动时刻数据库的状态,事务无奈感知到其它事务对数据库所做的变更。
可序列化(Serializable事务对数据的操作会加锁,通过加锁使事务串行化执行,是最高事务隔离级别,但数据库的并发能力最低。

特地阐明:在MySQLInnoDB引擎中,以读未提交隔离级别为例,某个事务如果隔离级别是读未提交,并不阐明该事务在提交事务前对数据库所做的变更对其它事务可见,而是该事务查问数据时抉择将其它未提交事务对数据库所做的变更置为可见。即事务隔离级别是事务在查问数据时抉择所有被查数据的一种规定,局部文章中对事务隔离级别的概念进行了混同,故特此说明。

不同的隔离级别下,事务读数据一致性和并发事务带来的问题能够用下表示意。

上面将针对不同的事务隔离级别,基于MySQLInnoDB引擎进行简略的示例演示。首先创立表,SQL语句如下所示。

CREATE TABLE info(    id INT(11) PRIMARY KEY AUTO_INCREMENT,    num INT(11) NOT NULL)

插入一条数据以供查问,SQL语句如下所示。

INSERT INTO info (num) VALUES (20);

第一个示例是读未提交,先将事务2隔离级别设置为READ UNCOMMITTED,事务执行流程如下图所示。

事务2读取到了事务1未提交的数据,在事务1回滚之后,数据库中id为1的数据的num为20,此时事务2读取到的数据成为了脏数据,产生了脏读。

第二个示例是读已提交,先将事务2隔离级别设置为READ COMMITTED,事务执行流程如下图所示。

步骤3中事务1更新了id为1的数据的num为25,步骤4中事务2查问了id为1的数据且num为20,阐明在读已提交事务隔离级别下,事务无奈感知其它未提交事务对数据库的更改。步骤5中事务1提交了事务,步骤6中事务2又查问了id为1的数据且num为25,那么事务2在同一次事务中对同一条数据的两次读取后果不雷同,产生了不可反复读。

进行第三个示例前,先将id为1的数据的num更改回20。

第三个示例是可反复读,先将事务2隔离级别设置为REPEATABLE READ,事务执行流程如下图所示。

事务1更改了id为1的数据的num为25,并提交了事务,事务2在事务1提交事务前后别离执行了一次查问,并且第二次查问后果与第一次查问后果雷同,所以在可反复读隔离级别下解决了不可反复读问题。

进行第四个示例前,先将id为1的数据的num更改回20。

第四个示例仍旧是可反复读,事务执行流程如下图所示。

步骤3中事务1插入了一条num为25的数据,步骤4中事务2执行了一次范畴查问,查问后果不蕴含num为25的数据,步骤5中事务1提交了事务,步骤6中事务2又执行了一次范畴查问,查问后果还是不蕴含num为25的数据,所以在MySQLInnoDB引擎下将事务隔离级别设置为可反复读时,还能够解决幻读的问题。通常状况下,可反复读隔离级别是无奈解决幻读的,然而MySQLInnoDB引擎应用了MVCC技术,能够在可反复读隔离级别下,躲避了幻读的问题,对于MVCC技术,将在下一大节进行阐明。

四. 一致性非锁定读取

对于一致性非锁定读取的官网概念,能够参见MySQL的官网文档:Consistent Nonlocking Reads。定义如下所示。

A consistent read means that InnoDB uses multi-versioning to present to a query a snapshot of the database at a point in time. The query sees the changes made by transactions that committed before that point in time, and no changes made by later or uncommitted transactions.The exception to this rule is that the query sees the changes made by earlier statements within the same transaction.

一致性读取意味着InnoDB引擎应用多版本控制在某个工夫点向查问操作提供数据库的快照。查问能够看到在该工夫点之前提交的事务所做的更改,而不会看到稍后或未提交的事务所做的更改。此规定的例外情况是,查问能够看到同一事务中晚期语句所做的更改。

MySQLInnoDB引擎中,一致性非锁定读取利用在读已提交可反复读隔离级别上,区别在于读已提交隔离级别下,每次查问语句执行时,均会基于以后数据库的最新状态生成快照,而可反复读隔离级别下,只会在第一次查问时基于以后数据库的最新状态生成快照。

MySQLInnoDB引擎中的一致性非锁定读取是基于MVCC技术,而MVCC技术实质是依附undo log快照机制来实现的。上面将对MySQLInnoDB引擎中的MVCC进行剖析。

1. undo log

undo log即回滚日志,在其中记录了某条数据变更前的旧数据。在MySQLInnoDB引擎中,每一条数据除了本来的字段外,还有三个暗藏字段,别离为DB_TRX_IDDB_ROLL_PTRDB_ROW_ID,这三个暗藏字段的含意如下所示。

暗藏字段含意
DB_TRX_ID最初一次变更(增删改)本行数据的事务的id
DB_ROLL_PTR指向本行数据的undo log的指针。
DB_ROW_ID本行数据的行id,与MVCC无关。

那么对于第三大节中的info表的一条数据,在MySQLInnoDB引擎中能够示意如下。

上述示例中,省略了DB_ROW_ID字段。对于上述示例中info表的id为1的数据,会存在多个版本,这些多个版本的数据会寄存于undo log中,比方事务id为2000的事务,执行如下操作。

START TRANSACTION;UPDATE info SET num=21 WHERE id=1;UPDATE info SET num=22 WHERE id=1;COMMIT;

那么info表的id为1的数据的多个版本之间存在如下关系。

info表的id为1的数据的多个版本之间通过DB_ROLL_PTR相连并形成了一个undo log回滚链,以undo log回滚链为根底能够保障事务的原子性(事务回滚)和隔离性(MVCC)

2. 快照

在MySQL官网文档快照阐明中对快照这一概念进行了阐明,如下所示。

A representation of data at a particular time, which remains the same even as changes are committed by other transactions. Used by certain isolation levels to allow consistent reads.

特定工夫的数据表示,即便其余事务提交更改也放弃不变。由某些隔离级别应用以容许统一读取。

因而官网是抵赖MVCC中的快照这一概念的,同时在局部博客文章中应用了Read View这一概念,并称之为一致性视图,依照MySQL官网文档里对快照的解释来看的话应该是生成快照时会联合以后数据库状态定义出Read View,而后基于Read View和肯定规定生成快照。快照只会在读已提交可反复读隔离级别下执行查问语句时生成,并且在读已提交隔离级别下,每次执行查问语句时均会生成快照,而在可反复读隔离级别下,只会在第一次执行查问语句时生成快照。实际上,这里的生成快照能够了解为决定可见的数据的范畴,快照生成结束,即以后事务可见的数据的范畴也确定,上面将联合一个示例进行阐明。

在示例演示之前,先阐明一下事务id的生成机会,已知能够通过如下语句开启事务。

START TRANSACTION;

但实际上只执行上述SQL语句并不会为以后事务调配id,详见下图。

可见事务id为空,实际上在事务中首次执行增删改查时,才会为事务调配id,如下所示。

最初阐明一下,事务id是严格递增的。

当初开始进行示例阐明。因为MySQL理论调配的事务id过长,所以上面的事务id均应用假设的事务id。具体操作如下所示。

事务2500在步骤9中执行查问操作时,会基于那一刻的数据库状态定义Read ViewRead View的重要组成内容如下表所示。

组成内容含意
m_ids定义Read View那一刻的数据库中所有未提交事务的id数组。
min_trx_idm_ids中的最小值。
max_trx_id定义Read View那一刻的数据库中的事务的最大id
creator_trx_id定义Read View的事务的id

因为事务id是严格递增的,所以Read View能够用下图进行示意。

事务生成快照时,快照中蕴含哪些数据(哪些数据以后事务可见),是基于Read Viewundo log回滚链决定的,对于每条数据,会先从其最新版本进行判断,如果判断不可见,则依据undo log回滚链找到旧版本并持续判断,如果某条数据所有版本都被判断为不可见,则阐明这条数据对以后事务不可见,快照中不会蕴含这条数据的任何版本。判断规定如下所示。

  • 如果某版本的数据的DB_TRX_IDRead Viewcreator_trx_id相等,阐明这个版本的数据最初由以后事务更改,故这个版本的数据对以后事务可见
  • 如果某版本的数据的DB_TRX_ID小于Read Viewmin_trx_id,阐明这个版本的数据最初由曾经提交的事务更改,故这个版本的数据对以后事务可见
  • 如果某版本的数据的DB_TRX_ID大于Read Viewmax_trx_id,阐明最初批改这个版本的数据的事务在快照生成时还未创立,故这个版本的数据对以后事务不可见
  • 如果某版本的数据的DB_TRX_ID满足:min_trx_id <= DB_TRX_ID <= max_trx_id,且m_ids蕴含DB_TRX_ID,阐明这个版本的数据最初由未提交的事务更改,故这个版本的数据对以后事务不可见
  • 如果某版本的数据的DB_TRX_ID满足:min_trx_id <= DB_TRX_ID <= max_trx_id,但m_ids不蕴含DB_TRX_ID,阐明这个版本的数据最初由曾经提交的事务更改,故这个版本的数据对以后事务可见

所以在示例中,事务2500在步骤9中执行查问操作时,基于那一刻的数据库状态定义进去的Read View能够示意如下。

min_trx_id = 2000,max_trx_id = 3000,m_ids = {2000, 3000}。在执行到步骤9时,info表的id为1的数据的undo log回滚链如下所示。

那么这条数据的最新版的DB_TRX_ID为3000,满足min_trx_id <= DB_TRX_ID <= max_trx_id,但m_ids蕴含DB_TRX_ID,所以最新版的数据对事务2500是不可见的,此时会依据DB_ROLL_PTR找到该条数据的旧版本即旧版二持续判断,因为该条数据的旧版二的DB_TRX_ID为1000,满足DB_TRX_ID <= min_trx_id,所以旧版二的数据对事务2500是可见的,即步骤9的快照中会蕴含该条数据的旧版二。同理,在执行到步骤9时,info表的id为2的数据的undo log回滚链如下所示。

那么这条数据的最新版的DB_TRX_ID为2000,满足min_trx_id <= DB_TRX_ID <= max_trx_id,但m_ids蕴含DB_TRX_ID,所以最新版的数据对事务2500是不可见的,此时会依据DB_ROLL_PTR找到该条数据的旧版本即旧版一持续判断,因为该条数据的旧版一的DB_TRX_ID也为2000,所以旧版本的数据对事务2500也是不可见的,即步骤9的快照中不会蕴含该条数据的任何版本。

最终步骤9的查问后果如下所示。

可见步骤9中事务2500将快照中的所有数据查问了进去,同时快照中的数据与上述的探讨是吻合的。

在步骤10中,事务2000提交了事务,在步骤11中,事务2500又执行了一次查问,因为事务2500的事务隔离级别为可反复读,因而步骤11中事务2500执行查问并生成快照时定义的Read View和步骤9中一样,所以只管事务2000曾经由未提交事务变更为了已提交事务,然而步骤11中事务2500执行查问的查问后果应该和步骤9中的一样,如下所示。

如果事务2500隔离级别更改为读已提交,如下所示。

事务2500的隔离级别为可反复读或读已提交时,步骤9的查问后果应该一样,然而步骤11的查问后果会有不同,读已提交隔离级别下步骤11的查问后果如下所示。

呈现上述后果的起因就是在可反复读隔离级别下,每次查问生成快照时依赖的Read View不会随着数据库状态的扭转而扭转,然而读已提交隔离级别下会随着数据库状态的扭转而扭转,所以读已提交隔离级别下,每次查问生成快照时,只有数据库状态产生了扭转,那么Read View就会扭转,从而本次查问的快照可能会和上一次查问的快照不统一,最终导致读已提交隔离级别下一个事务中的两次查问的查问后果不一样,产生了不可反复读。

3. 大节

对于一致性非锁定读取,能够进行如下大节。

  • MySQLInnoDB引擎中的一致性非锁定读取利用在读已提交可反复读隔离级别上,并且实质是基于MVCC技术进行实现;
  • MySQLInnoDB引擎中的MVCC技术是依附undo log快照实现;
  • MySQLundo log会将某条数据的多个版本进行链接从而造成一个回滚链undo log回滚链MVCC技术的根底,同时也是事务回滚的根底;
  • MySQL的事务生成的快照就是那一刻事务所能看到的数据库的状态,后续无论其它事务如何对数据库进行操作,生成快照的事务只能看到快照所展现的数据库的状态;
  • MySQLInnoDB引擎中的一致性非锁定读取是无锁读取,事务不会对读取的数据行加锁。

同时,也能够参考官网文档里对统一读取的定义:consistent read。

总结

事务就是由一组SQL语句组成的逻辑处理单元。事务的ACID模型如下表所示。

ACID属性项解释
原子性(Atomicity事务是一个原子操作单元,其对数据的批改,要么全都执行,要么全都不执行。
一致性(Consistent在事务开始和实现时,数据都必须保持一致状态。即所有相干的数据规定都必须利用于事务的批改,以保持数据的完整性;事务完结时,所有的外部数据结构(如B树索引或双向链表)也都必须是正确的。
隔离性(Isolation数据库系统提供肯定的隔离机制,保障事务在不受内部并发操作影响的“独立”环境执行。即事务处理过程中的中间状态对其它事务是不可见的。
持久性(Durable事务实现之后,事务对于数据的批改是永久性的,即便呈现系统故障也可能放弃。

并发执行事务会晋升数据库效率,然而会导致一些并发问题,如下表所示。

并发事务带来的问题解释
脏读(Dirty Reads一个事务正在对一条记录做批改,在这个事务实现并提交前,这条记录的数据就处于不统一状态,这时,第二个事务来读取同一条记录,如果不加管制,第二个事务会读取这条处于不统一状态的记录,即读取到脏数据,称为脏读
不可反复读(Non-Repeatable Reads一个事务在读取某些数据后的某个工夫,再次读取以前读过的数据,却发现其读出的数据曾经产生了扭转或某些记录曾经被删除,这种景象称为不可反复读
幻读(Phantom Reads一个事务按雷同的查问条件从新读取以前检索过的数据,却发现其它事务插入了满足其查问条件的新数据,这种景象称为幻读

为了肯定水平上解决上述问题并同时兼顾数据库效率,引入了事务隔离级别这一概念,不同的隔离级别会导致不同的事务并发问题,每种隔离级别的形容如下表所示。

事务隔离级别形容
读未提交(Read uncommitted,RU事务能够感知到其它未提交事务对数据库所做的变更。
读已提交(Read committed,RC事务无奈感知到其它未提交事务对数据库所做的变更。
可反复度(Repeatable read,RR事务在执行过程中,只能看到事务启动时刻数据库的状态,事务无奈感知到其它事务对数据库所做的变更。
可序列化(Serializable事务对数据的操作会加锁,通过加锁使事务串行化执行,是最高事务隔离级别,但数据库的并发能力最低。

不同的隔离级别下,事务读数据一致性和并发事务带来的问题能够用下表示意。

MySQLInnoDB引擎中,一致性非锁定读取利用在读已提交可反复读隔离级别上,一致性非锁定读取是基于MVCC技术实现的无锁读取,事务不会对读取的数据行加锁。