浅谈事务隔离级别、MVCC及相关特性

文采不是很好,应该有一些表达不清楚的地方,请改正。

[En]

Literary talent is not very good, there should be some unclear expression, please correct.

需要事先准备测试表:

CREATE TABLE test (

id int(11) NOT NULL,

name varchar(10) DEFAULT NULL,

age int(11) DEFAULT NULL,

PRIMARY KEY (id)

) ENGINE=InnoDB DEFAULT CHARSET=utf8

插入数据:

insert into test values(1,’张一’,20),(3,’王三’,10),(4,’李四’,34);

一、ACID特性

说起事务我们肯定会先想到事务的ACID四大特性:Atomicity、Consistency、Isolation、Durability

A(Atomicity):原子性,对事务的状态要么全部提交,要么全部回滚,没有中间状态;

C(Consistency):一致性,事务开始前和结束后,数据库的完整性没有被破坏;

I(Isolation):隔离性,允许多个并发事务同时对数据库操作;

D(Durability):持久性,事务提交后,对数据的修改就是永久的。

今天主要介绍的就是I(隔离性)。

二、MySQL隔离级别

在讨论隔离级别之前,我们先简要介绍几种读取操作的方法:

[En]

Before talking about the isolation level, let’s briefly introduce several ways to read the operation:

①普通读取,也就是普通的select查询操作;

②锁定读取,也就是select … lock in share mode; select … for update; update等;

需要注意的是,本文仅针对普通阅读,不涉及锁读操作,因为锁读会涉及锁相关信息,所以本文不做过多的解释和验证。

[En]

Note that this article is only for ordinary reading and does not involve locking read operations, because locking reading will involve lock-related information, so this article will not do too much explanation and verification.

mysql中隔离级别有如下几类:

READ UNCOMMITTED(RU)、READ COMMITTED(RC)、REPEATABLE READ(RR)、SERIALIZABLE

其实我们根据单词的意思就能知道这4种隔离级别的表面的含义了:RU读未提交、RC读已提交、RR可重复读、SERIALIZABLE串行化。每个隔离级别都有自己的特点,可根据实际业务需求情况来进行选择,5.7版本默认的是RR隔离级别。

为什么叫隔离级别呢?

根据个人的理解,主要是对不同事务之间的相互影响程度所采取的一种措施,根据级别的由低到高(RU–>RC–>RR–>SERIALIZABLE),所产生的影响程度不断提高(也就是并发度越来越低,效率越来越低),可以说每种隔离级别都有自己的特点,在介绍隔离级别的特点的时候,我们先来理解几个名词的概念:

①脏读:读取其他未提交事务的数据,但此事务可能被提交或回滚,如果回滚,将不会存储在数据库中,因此也可以说是读取了可能不存在的数据。

[En]

① dirty read: read the data of other uncommitted transactions, but this transaction may be committed or rolled back, if rolled back, it will not be stored in the database, so it can also be said to read data that may not exist.

②不能重复读取:相同的事务读取相同的数据,专注于修改。事务开始时读取的数据与事务结束前的任何时候读取的数据不一致。

[En]

② can not be read repeatedly: the same transaction reads the same data, focusing on modification. The data read at the beginning of the transaction is inconsistent with the data read at any time before the end of the transaction.

③幻读:其实在幻读和不可重复读之间有一些不太好理解,不可重复读侧重的是两次读取操作的结果不同(该事务读取到了其他事务修改后并提交的数据)。而幻读更侧重的是插入,就是读取到了之前没有读到过的数据,比如A事务先读取某个范围内的数据,此时B事务在该范围内插入数据并提交,而A事务再次读取该范围内的数据的时候发现和第一次读取的不一致了,发现数据多了,读到了之前没有读到的数据。

了解了以上概念后,让我们测试一下不同隔离级别下的特性。

[En]

After knowing the above concepts, let’s test the characteristics under different isolation levels.

1、RU隔离级别(读未提交):

读取未提交意味着当一个事务未提交时,其更改将被其他事务看到。在这种隔离级别下,将发生脏读、幻读和不可重复读。通过打开不同的交易进行测试:

[En]

Reading uncommitted means that when a transaction is not committed, its changes will be seen by other transactions. Under this isolation level, dirty reading, phantom reading, and unrepeatable reading will occur. Test by opening different transactions:

首先修改隔离级别为RU:set global transaction isolation level read uncommitted;

①脏读:

②不可重复读:

③幻读:

2、RC隔离级别(读已提交):

READ COMMITTED,意味着一个事务提交后,其他事务可以看到它的更改。在此隔离级别下不会有脏读,但它可能会产生幻影和不可重复的读。通过打开不同的交易进行测试:

[En]

Read committed, meaning that after one transaction commits, other transactions can see its changes. There is no dirty reading under this isolation level, but it can produce phantom and unrepeatable reads. Test by opening different transactions:

首先修改隔离级别为RC:set global transaction isolation level read committed;

①脏读:

可以看到在session B中的事务未提交的时候,它所做的更改在session A中的事务始终是不可见的,所以不存在脏读。

②不可重复读:

③幻读:

3、RR隔离级别(可重复读):

可重复,这意味着在事务开始和结束期间看到的数据是一致的,事务修改的数据在事务结束之前对其他事务不可见。在此隔离级别下,不会发生脏读和不可重复读,但可能会出现幻读。测试通过打开不同的交易来进行。在这里,只测试幻影读数:

[En]

Repeatable, which means that the data seen is consistent during the beginning and end of the transaction, and the data modified by the transaction is not visible to other transactions until the transaction ends. Under this isolation level, dirty reading and non-repeatable reading will not occur, but phantom reading may occur. Test is carried out by opening different transactions. Here, only phantom reading is tested:

首先修改隔离级别为RR:set global transaction isolation level repeatable read;

①不会产生幻读的情况:

可以看到在RR隔离级别下,只是在普通读取过程中不会产生幻读的情况,因为Session A中的事务在其他事务提交前后都是读取到的相同的记录,并没有读取到新增的记录。

②会产生幻读的情况:

该普通读取产生了幻读,是因为在Session A中的事务执行了一次update操作,该update操作属于是当前读,其他事务已经提交过的sql,在该事务中是可以被当前读所读到的。但是在RR隔离级别下是可以加锁来解决幻读的。

4、SERIALIZABLE隔离级别(串行化):

在该隔离级别下,使用读加写、写和写锁来防止脏读、幻读和非重复读。这里不会做相关的实验,有兴趣的人可以修改隔离级别进行测试。

[En]

Under this isolation level, read plus write, write and write locks are used to prevent dirty reading, phantom reading and non-repetitive reading. No related experiments will be done here, and those who are interested can modify the isolation level for testing.

三、MVCC

上面的实验都是基于普通的select语句进行的,由于其他的当前读会涉及到比较繁琐的加锁过程,所以本文没有阐述。

普通的select语句在RC和RR隔离级别下属于快照读,这就是由MVCC所控制了,MVCC只在RC和RR隔离级别下存在。

1、undo链:

MVCC又称为多版本并发控制,就是在每行记录中会添加行的创建版本号和过期版本号:

①trx_id:该id就是当前事务的id,对记录进行操作的时候都会把当前事务id赋值给trx_id;

②roll_ptr:是回滚指针,指向undo的,当某条记录被修改后,该记录的旧版本会写入undo,此时该记录的roll_id会指向对应的undo。

比如我们此时在test表中插入一条记录,假设此时版本号为10:

insert into test values(6,’王六’,66); //其实insert undo信息只是起到的回滚的作用,当事务提交后就会删除了。

比如我们在两个事务中update一条记录,假设该记录开始版本号为15:

此时应该是这样的:

这样的话就形成了一个undo版本链儿。

注意上面的箭头并不是代表指向的某个字段记录,而是在undo中的整条记录。

2、read view:

我们通过上面的一些测试,知道了RC和RR隔离级别下都是其它事务提交之后才能读到,那没有提交的时候,其它事务是如何进行读取历史的版本的呢?这就产生了read view(读视图)。

read view中几个比较重要的部分:当前活跃的事务id列表、活跃事务id列表中最小活跃的事务id(记为min_id)、当前的事务id(记为creat_id)、下一个事务id号(max_id)。

这里要知道事务id是递增的,只有在事务真正修改记录的时候才会被分配。

则在访问数据时会做出如下判断:

[En]

Then the following judgment will be made when accessing the data:

①被访问的记录的trx_id是否和creat_id一致,如果一致的话,说明是在当前事务中进行读取的,也就是访问的自己本身;

②如果被访问的记录的trx_id小于min_id,表明该记录修改已经在当前事务生成read view之前提交了,也是可以被访问到的;

③如果被访问的记录的trx_id大于max_id,代表该事务是在当前事务生成read view之后开启的,是不能访问的;

④如果被访问的记录的trx_id在min_id 和 max_id之间,那么就要判断一下该事务id是不是在活跃事务id列表中,如果存在的话,说明生成当前事务的read view的时候该事务还是活跃的,是不可见的。如果不存在的话,说明在生成当前事务的read view的时候该事务已经提交了是可见的。

3、RC、RR中的read view:

在RC和RR中生成read view是有些不同的,主要是生成的时间点不同。

1)RC隔离级别下是每次读取数据的时候都会生成一个read view

此时对应undo链如下:

当Session C事务在RC隔离级别下,去读取id=3的这条记录,那么它读取出来的是trx_id为15的这条版本记录。

原因:

①在Session C事务进行读取的时候,首先生成read view,该read view中包含一个当前活跃的事务id列表也就是[20,40],min_id是20,max_id是41,creat_id是0;

②Session C的事务只是读取,并没有修改,所以creat_id为0,最新的事务版本id是20,存在于当前活跃的链表中所以看不到该条记录;

③继续沿着undo链表往下走,trx_id还为20,还在活跃的链表中,所以也读不到;

④继续沿着undo链表往下走,trx_id为15,小于min_id 20所以可见。

继续测试,将SessionA中的事务进行提交,在Session B中修改该记录:

对应的undo链就是:

继续在Session C的事务中去读取id=3的这条记录,那么它读取出来的是trx_id为20的这条记录版本。

因为RC每次读取都会生成新的read view,此时的read view中当前活跃事务id列表也就是[40],min_id是40,max_id是41,creat_id是0。该记录的最新事务版本id是40,在活跃列表中,所以是可以不可以读到的。继续往下走事务id是20不在活跃列表中,是可以读到的。

2)RR隔离级别下是在事务开始的时候都一个read view,在事务结束之前都会去读取该read view

还是以上面的例子为例:

此时对应undo链如下:

当Session C事务在RR隔离级别下,去读取id=3的这条记录,那么它读取出来的是trx_id为15的这条版本记录。

原因(截止到现在RR隔离级别下和RC是一样的原理):

①在Session C事务进行读取的时候,首先生成read view并且之后都会使用该read view,该read view中包含一个当前活跃的事务id列表也就是[20,40],min_id是20,max_id是41,creat_id是0;

②Session C的事务只是读取,并没有修改,所以creat_id为0,最新的事务版本id是20,存在于当前活跃的链表中所以看不到该条记录;

③继续沿着undo链表往下走,trx_id还为20,还在活跃的链表中,所以也读不到;

④继续沿着undo链表往下走,trx_id为15,小于min_id 20所以可见。

继续测试,将SessionA中的事务进行提交,在Session B中修改该记录:

对应的undo链就是:

继续在Session C的事务中去读取id=3的这条记录,那么它读取出来的还是trx_id为15的这条记录版本。

因为RR还在使用的是事务开始时候的read view,此时的read view中当前活跃事务id列表也还是[20,40],min_id是20,max_id是41,creat_id是0。该记录的最新事务版本id是40,在活跃列表中,所以是可以不可以读到的。继续往下走事务id是20还在活跃列表中,是不可以读到的,直到读到事务id是15的时候才可以读到。

Original: https://www.cnblogs.com/ordinarydba/p/16284617.html
Author: DB文档搬运工
Title: 浅谈事务隔离级别、MVCC及相关特性

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/505369/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球