Loading... 今天介绍下,在 `MySQL` 的 `InnoDB` 存储引擎中,事务隔离是如何实现的。 `InnoDB` 里面每个事务有一个唯一的事务 `ID`,叫作 `transaction id`。它是在事务开始的时候向 `InnoDB` 的事务系统申请的,是按申请顺序严格递增的。 对于数据库的每行记录,都会有三个隐藏字段:`db_trx_id (事务 id)`、`db_roll_pt (回滚指针)`、`delete_flag(删除标记)`。— *有懂的朋友,还望别细纠,其实 `delete_flag` 是在头信息中,这里是为了方便理解* 对于 `DML` 操作来说: - **INSERT**:创建一条数据,`db_trx_id` 的值为当前事务 `id`, `db_roll_pt` 为 `null` 。 - **UPDATE**:复制一行数据,将当前复制后这一行的 `db_trx_id` 置为当前事务的 `id`,`db_roll_pt` 是一个指针,指向复制前的那一条的。 - **DELETE**:复制一行数据,将当前复制后这一行的 `db_trx_id` 置为当前事务的 `id`,`db_roll_pt` 是一个指针,指向复制前的那一条的。并把 `delete_flag` 置为 `true` 。 <!-- more --> 我们会用此语句建表及初始化数据,用于下面举例: ```sql 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); ``` 数据初始状态为: ![](https://cdn.jun6.net/2020/03/01/1ef1b7a3efa33.png) ### Repeatable Read 可重复读 如当前的隔离界别为 `Repeatable Read` ,下面是 SQL 的执行过程: ![](http://cdn.jun6.net/2020/03/01/3996297518139.png) 那么对于第 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 view` 及 `max id`,要注意的是,这里我并没有使用 `begin/start transaction` 来开启事务,是因为它们并不会马上创建 `read view` 及 `max id`,而是在执行第一条 `select` 语句后,来进行创建的。 - 第 5 行为修改 k 的值,自增 1,按照上面所说的规则,修改后: ![](https://cdn.jun6.net/2020/03/01/cb277c85512e2.png) - 第 6 行,提交 ID 为 102 的事务。 - 第 7 行执行了一个简单查询,未手动开启事务,但也会自动开启并生成 `read view` 及 `max 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_id` 在 `read view` 的数组中,表示这个版本是由还没提交的事务生成的,不可见,但如果是自己的事务,则可见。 3.2. 若不在数据中,则表示这个版本是已经提交了的事务生成的,可见。 示意图如下: ![](http://cdn.jun6.net/2020/03/01/a3e4de84498c1.png) 当前的事务的一致性视图为 `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,按照上面所说的规则,修改后: ![](http://cdn.jun6.net/2020/03/01/ffa5c0e9e2e7a.png) - 第 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 view` 和 `max id` 的创建时机,是每一条 `select` 语句时重新生成。你根据上面的内容,可以自己动手试验下读已提交。 最后修改:2022 年 05 月 02 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请我喝杯咖啡吧。