首先我们创建一张测试的数据表,表中的结构如下:
CREATE TABLE `user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
接着对表中插入一些模拟数据使用。
insert into `user` (`name`) values ('1'), ('1'), ('1'),
查询一下表中的数据状态,可以看到表中的数据是正常插入。
MySQL root@192.168.2.100:demo> select * from `user`;
+----+------+
| id | name |
+----+------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
+----+------+
3 rows in set
Time: 0.110s
接下来的几步演示非常重要,请阅读的你仔细按照文章的流程来进行操作(阅读)。在演示环境我们会打开两个终端对MySQL执行,模拟开启两个事务:
1、开启终端1
MySQL root@192.168.2.100:demo> set autocommit=0;
Query OK, 0 rows affected
Time: 0.070s
2、开启终端2
MySQL root@192.168.2.100:demo> set autocommit=0;
Query OK, 0 rows affected
Time: 0.024s
3、对终端1执行更新操作
MySQL root@192.168.2.100:demo> begin;
Query OK, 0 rows affected
Time: 0.051s
MySQL root@192.168.2.100:demo> select * from `user`;
+----+------+
| id | name |
+----+------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
+----+------+
3 rows in set
Time: 0.110s
MySQL root@192.168.2.100:demo> update `user` set name=2;
Query OK, 3 rows affected
Time: 0.029s
4、对终端2执行查询操作
MySQL root@192.168.2.100:demo> begin;
Query OK, 0 rows affected
Time: 0.051s
MySQL root@192.168.2.100:demo> select * from `user`;
+----+------+
| id | name |
+----+------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
+----+------+
3 rows in set
Time: 0.110s
此时我们发现事务2查询到的数据是一个快照读,并未读取到终端1执行修改操作后的结果。
接下来对终端1进行
commit
操作,再次在终端2执行select
操作,你会发现数据还仍然是快照读。读到这里,你可能会认为
MySQL默认的事务隔离级别是可重复读,当前事务是不能读取到其他事务修改的结果
。接下来在演示同样的示例,你会发现这个结论得到的结果是不正确的。
1、开启终端1
MySQL root@192.168.2.100:demo> begin;
Query OK, 0 rows affected
Time: 0.015s
2、开启终端2执行更新操作并提交
MySQL root@192.168.2.100:demo> begin;
Query OK, 0 rows affected
Time: 0.092s
MySQL root@192.168.2.100:demo> update `user` set name = '2';
Query OK, 3 rows affected
Time: 0.103s
MySQL root@192.168.2.100:demo> commit;
Query OK, 0 rows affected
Time: 0.185s
3、对终端1执行查询操作
MySQL root@192.168.2.100:demo> select * from `user`;
+----+------+
| id | name |
+----+------+
| 1 | 2 |
| 2 | 2 |
| 3 | 2 |
+----+------+
3 rows in set
Time: 0.032s
至于为什么会这样,这是因为MySQL的MVCC所导致,下来本文将重点分析MVCC实现的原理。什么是MVCC
隐藏字段介绍
隐藏字段包含有:
| 字段名称 | 字段说明 |
|---|---|
| DB_TRX_ID | 当前事务的ID,创建这条记录或者最后修改这条记录的事务ID |
| DB_ROLL_PTR | 事务回滚指针,指向数据的上一个版本,当事务进行回滚时可以通过该指针获取到原始的数据状态。 |
| DB_ROW_ID | 隐藏主键,如果数据库中没有显式的指定主键,MySQL会默认添加一个主键ID(row_id)。 |
在MySQL中一行完整的数据就如下构成。
| name | age | DB_TRX_ID | DB_ROLL_PTR | DB_ROW_ID |
|---|---|---|---|---|
| Tony | 12 | 1 | null | 1 |


这样整个undolog就形成了一个类似链表的结构。链首的是最新的最旧记录,链尾的是最旧的旧记录。对于这样结构的undolog,你可能会想到一个问题,如果是多个事务,是否事务的日志文件会无线的增大,答案肯定是不会的。在MySQL内部有一个单独的线程,叫做purge线程,会单独的去维护undolog日志。关于purge线程,你可以通过<<MySQL技术内幕 InnoDB存储引擎>>一书的317页进行阅读。
readview


可见性算法。可见性算法的规则如下:
1. 首先比较DB_TRX_ID < up_limit_id。如果是小于,则当前事务可以看到DB_TRX_ID所在的记录,如果大于则进行下一个判断。
2. 接下来判断DB_TRX_ID >= low_limit_id。如果符合给条件,则DB_TRX_ID所在的记录是在readview视图生成之后才出现的,
那么对于当前事务是不可见的,如果小于则进行下一步判断。
3. 判断DB_TRX_ID是否存在当前活跃事务中,
如果存在,则表示readview生成时刻,该事务还在活跃状态,还没有commit。修改的数据,当前事务也是看不到。
如果不存在,则说明这个事务在readview生成之前已经进行了commit,那么修改的结果是可以看到的。

DB_TRX_ID < up_limit_id)。

在提交读的事务隔离级别中,每一次快照读生成都会生成一个 readview
,因此当别的事务进行了提交,当前事务是可以读取到更新后的结果。在可重复读的事务隔离级别中,虽然每一次快照读都会生产一个 readview
,但只沿用第一次生成的快照读。这也是为什么第一个代码演示不可读取到其他事务提交的数据,就是因为读取的readview都是第一次生成的视图。
文章转载自卡二条的技术圈,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




