8

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

 3 years ago
source link: http://www.zhaojun.im/mysql-mvvc/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

2020-03-01

| MySQL

| 3235

| 691

今天介绍下,在 MySQLInnoDB 存储引擎中,事务隔离是如何实现的。

InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。

对于数据库的每行记录,都会有三个隐藏字段:db_trx_id (事务 id)db_roll_pt (回滚指针)delete_flag(删除标记)。— 有懂的朋友,还望别细纠,其实 delete_flag 是在头信息中,这里是为了方便理解

对于 DML 操作来说:

  • INSERT:创建一条数据,db_trx_id 的值为当前事务 id, db_roll_ptnull
  • UPDATE:复制一行数据,将当前复制后这一行的 db_trx_id 置为当前事务的 iddb_roll_pt 是一个指针,指向复制前的那一条的。
  • DELETE:复制一行数据,将当前复制后这一行的 db_trx_id 置为当前事务的 iddb_roll_pt 是一个指针,指向复制前的那一条的。并把 delete_flag 置为 true

我们会用此语句建表及初始化数据,用于下面举例:

1
2
3
4
5
6
7
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`k` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;

insert into t(id, k) values(1, 1);

数据初始状态为:

loading2.gif

Repeatable Read 可重复读

如当前的隔离界别为 Repeatable Read ,下面是 SQL 的执行过程:

loading2.gif

那么对于第 8,10,12,13 行来说,查询的结果应该是什么呢?

结果应该分别是:2,1,3,1 。

下面我们来逐步回放,MySQL 底层是如何实现这整个过程的:

  • 第 1 行:表示每个事务的 ID 号,其中 read view 取的是所有当前活跃的事务 ID 数组,活跃指的是,已开启并生成事务 ID 但未提交的事务。max id 取的是,目前为止,最大的事务 ID,不论是否已提交。我们还称 read view 数组中,最小的值为 min id
  • 第 2 - 4 行:表示分别开启使用,并创建此事务的 read viewmax id,要注意的是,这里我并没有使用 begin/start transaction 来开启事务,是因为它们并不会马上创建 read viewmax id,而是在执行第一条 select 语句后,来进行创建的。
  • 第 5 行为修改 k 的值,自增 1,按照上面所说的规则,修改后:

    loading2.gif
  • 第 6 行,提交 ID 为 102 的事务。

  • 第 7 行执行了一个简单查询,未手动开启事务,但也会自动开启并生成 read viewmax id,分别为 read-view: [100, 101]max-id: 103

此时会根据查询规则,进行查找,规则如下:

  1. 如果数据的 db_trx_id < min id ,则说明数据在开启当前事务前已提交的,内容可见。

  2. 如果数据的 db_trx_id > max id ,则说明数据在此事务启动后生成的,内容不可见。

  3. 如果数据的 min id <= db_trx_id <= max id ,则还分为两种情况:

    3.1 若 db_trx_idread view 的数组中,表示这个版本是由还没提交的事务生成的,不可见,但如果是自己的事务,则可见。

    3.2. 若不在数据中,则表示这个版本是已经提交了的事务生成的,可见。

示意图如下:

loading2.gif

当前的事务的一致性视图为 read view: [100, 101]max id: 103,那么根据这个规则,在上面的数据链中查询数据,从最新的蓝色,开始找,找到第一个数据的 db_trx_id 为 102,符合规则 3.2 属于可见范围,查询结果为 2。

  • 第 8 行,当前的事务的一致性视图为 read view: [100, 101]max id: 101同样根据规则,第一个数据的 db_trx_id 为 102,符合规则 2,不可见,那么根据指针 db_roll_pt 继续查找,找到 db_trx_id 为 10 的数据,符合规则 1,数据可见,查询结果为 1。
  • 第 9 行,修改 k 的值,自增 1,按照上面所说的规则,修改后:

    loading2.gif
  • 第 10 行,当前的事务的一致性视图为 read view: [100]max id: 100 同样根据规则,第一个数据的 db_trx_id 为 100,符合规则 3.1,在 read view 数组中,但是此 id 为当前事务 id,所以可是可见的,查询结果为 3。

  • 第 11 行,当前的事务的一致性视图为 read view: [100, 101]max id: 101 同样根据规则,第一个数据的 db_trx_id 为 100,符合规则 3.1,在 read view 中,但是此 id 不为当前事务 id,所以内容可见的,那么根据指针 db_roll_pt 继续查找,找到 db_trx_id 为 102 的数据,符合规则 2,不可见,继续根据指针 db_roll_pt 查找,找到 db_trx_id 为 10 的数据,符合规则 1,数据可见,查询结果为 1。
  • 第 12 - 13 行,为提交事务语句。

Read Committed 读已提交

处于 Read Committed 读已提交 也可套用上面的规则,不过一致性视图: read viewmax id 的创建时机,是每一条 select 语句时重新生成。你根据上面的内容,可以自己动手试验下读已提交。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK