前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MySQL多版本并发控制(MVCC)详解

MySQL多版本并发控制(MVCC)详解

作者头像
Mandy的名字被占用了
发布2023-02-28 10:00:55
6230
发布2023-02-28 10:00:55
举报

首先我们创建一张测试的数据表,表中的结构如下:

代码语言:javascript
复制
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

接着对表中插入一些模拟数据使用。

代码语言:javascript
复制
insert into `user` (`name`) values ('1'), ('1'), ('1'),

查询一下表中的数据状态,可以看到表中的数据是正常插入。

代码语言:javascript
复制
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

代码语言:javascript
复制
MySQL root@192.168.2.100:demo> set autocommit=0;
Query OK, 0 rows affected
Time: 0.070s

2、开启终端2

代码语言:javascript
复制
MySQL root@192.168.2.100:demo> set autocommit=0;
Query OK, 0 rows affected
Time: 0.024s

3、对终端1执行更新操作

代码语言:javascript
复制
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执行查询操作

代码语言:javascript
复制
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

代码语言:javascript
复制
MySQL root@192.168.2.100:demo> begin;
Query OK, 0 rows affected
Time: 0.015s

2、开启终端2执行更新操作并提交

代码语言:javascript
复制
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执行查询操作

代码语言:javascript
复制
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

此时你会发现,在事务2中对数据做了更改操作,事务1在开启阶段读取到了事务2的更新操作。那这样就违背了我们所熟知的事务可重复读隔离级别的理论知识。至于为什么会这样,这是因为MySQL的MVCC所导致,下来本文将重点分析MVCC实现的原理。

什么是MVCC

MVCC是MySQL在并发场景下,为了避免读写冲突的一种实现机制。其有隐藏字段、undolog、readview几部分组成。核心是在每一行的数据上添加一个版本号,以达到并发控制。

隐藏字段介绍

  • 隐藏字段包含有:

字段名称

字段说明

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的信息。假设我们开启3个事务,第一个事务执行插入操作,后面两个事务执行更新操作,下面的图则表示undolog内部存储的结构。

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

readview

readview是事务在进行快照读时产生的读视图,读视图保存的并非是实际的数据,而是关于事务之间的信息。内部也包含了几个元素字段。

接着我们使用上面演示的SQL实例2,当前事务能读取到其他已经提交的事务流程,当事务1进行select快照读时,就会生成一个完整的readview视图结构,整体的图形如下:

通过readview的原理,我们很轻松的可以得到上面的结构图。那为什么事务1能够读取到事务2提交的记录呢?这是因为MySQL底层还有一种被叫做可见性算法。可见性算法的规则如下:

代码语言:javascript
复制
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,那么修改的结果是可以看到的。

接着我们使用上面演示的SQL实例1,当前事务不能读取到其他已经提交的事务流程,当事务1进行select快照读时,就会生成一个完整的readview视图结构,整体的图形如下:

根据可见性算法规则,我们可以推算出,DB_TRX_ID还存在当前活跃事务中并未提交,因此当前事务是看不到其他事务修改的记录。

可能你有一个疑问,在tx5时刻生成一个快照,按照可见性算法的规则当前事务是可以读取到事务修改的记录(DB_TRX_ID < up_limit_id)。

产生这种现象的根本性原因在与,readview生成的时机。可直接记住下面的规则:

  • 在提交读的事务隔离级别中,每一次快照读生成都会生成一个readview,因此当别的事务进行了提交,当前事务是可以读取到更新后的结果。
  • 在可重复读的事务隔离级别中,虽然每一次快照读都会生产一个readview但只沿用第一次生成的快照读。这也是为什么第一个代码演示不可读取到其他事务提交的数据,就是因为读取的readview都是第一次生成的视图。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-02-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 菜鸟成长学习笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是MVCC
  • 隐藏字段介绍
  • readview
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档