首先我们创建一张测试的数据表,表中的结构如下:
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进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。
评论
相关阅读
【MySQL 30周年庆】MySQL 8.0 OCP考试限时免费!教你免费领考券
墨天轮小教习
3315次阅读
2025-04-25 18:53:11
MySQL 30 周年庆!MySQL 8.4 认证免费考!这次是认真的。。。
严少安
908次阅读
2025-04-25 15:30:58
【活动】分享你的压箱底干货文档,三篇解锁进阶奖励!
墨天轮编辑部
535次阅读
2025-04-17 17:02:24
MySQL 9.3 正式 GA,我却大失所望,新特性亮点与隐忧并存?
JiekeXu
461次阅读
2025-04-15 23:49:58
3月“墨力原创作者计划”获奖名单公布
墨天轮编辑部
383次阅读
2025-04-15 14:48:05
MySQL 8.0 OCP 1Z0-908 考试解析指南(二)
JiekeXu
353次阅读
2025-04-30 17:37:37
MogDB 发布更新,解决 openGauss 数据库在长事务情况下Ustore表膨胀问题
MogDB
309次阅读
2025-04-17 10:41:41
记录MySQL数据库的一些奇怪的迁移需求!
陈举超
304次阅读
2025-04-15 15:27:53
SQL优化 - explain查看SQL执行计划(下)
金同学
297次阅读
2025-05-06 14:40:00
MySQL 30 周年庆!MySQL 8.4 认证免费考!这次是认真的。。。
数据库运维之道
294次阅读
2025-04-28 11:01:25