前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MySQL之MVCC初探(1)

MySQL之MVCC初探(1)

作者头像
AsiaYe
发布2019-11-06 17:49:40
4090
发布2019-11-06 17:49:40
举报
文章被收录于专栏:DBA随笔
//

MySQL之MVCC初探(1)

//

MVCC初探---结合案例

昨天的文章中,我们说了MVCC的基本概念,然后讲了记录额外的两个字段,今天我们通过例子来说明一下MVCC在实际应用中的表现。我们首先创建一张表,然后插入一条数据:

代码语言:javascript
复制
mysql:yeyztest 21:48:49>>show create table swordsman\G
*************************** 1. row ***************************
       Table: swordsman
Create Table: CREATE TABLE `swordsman` (
  `id` int() NOT NULL AUTO_INCREMENT,
  `name` varchar() DEFAULT NULL,
  `kongfu` varchar() DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT= DEFAULT CHARSET=utf8
 row in set (0.00 sec)

mysql:yeyztest ::>>select * from swordsman;
+----+-----------+-----------------+
| id | name      | kongfu          |
+----+-----------+-----------------+
|  1 | 张无忌    | 乾坤大挪移      |
+----+-----------+-----------------+
1 row in set (0.00 sec)

这里我们插入一条数据,这条数据有三个字段,分别id,名称和武功,我们看一下这条记录是如何存储的:

可以看到,除了已有的三个字段外,后面还有两个字段,分别是trx_id和roll_pointer,这两个字段就是我们昨天说的隐藏列,其中trx_id保存的是记录的创建版本号,roll_pointer里面是一个指针,它指向了一个insert的undo日志,这个日志中保存的是之前的版本号。这里需要注意,这个undo日志只在事务执行的过程中存在,一旦这个insert事务提交完毕了,这条insert undo日志也就不存在了,系统会自动回收。

在现在这个情况下,我们假设对这条记录做一个改动,先看看改动的先后顺序:

这里我们看到两个事务对这个记录进行了更改,其中第一个事务的事务id是20,第二个事务的事务id是30,此时这个记录就变成了:

代码语言:javascript
复制
mysql:yeyztest 22:12:07>>select * from swordsman;
+----+--------+-----------------+
| id | name   | kongfu          |
+----+--------+-----------------+
|  1 | 杨逍   | 乾坤大挪移      |
+----+--------+-----------------+
1 row in set (0.00 sec)

这很好理解,因为第二个事务的提交时间比第一个晚,所以最终的name的值变成了"杨逍",这个时候,这条记录其实已经有很多版本了。画出来就是:

其实也就是之前的trx_id为10和20的版本也都存在于这个版本链里面了。如果我们对这条记录再次进行更新,这个版本链将会越来越多,这个版本链表的头结点就是当前最新的记录值。这样随着版本越来越多,一个重要的问题就出现了:在并发事务的时候,如何判断到底当前版本链中的哪一个版本对当前事务是可见的。

这个也是多版本并发控制的核心,我们知道事务有四个隔离级别,其中Read uncommitted级别是可以读取到其他未提交事务修改的记录,最高级别的serializable事务来说,innodb中是通过加锁的方式来访问记录的,暂时不考虑这种情况这样一来,我们只需要考虑在RC隔离级别和RR隔离级别下的情况了

这里我们提出一个读视图的概念,也可以称之为ReadView,在这个概念中,包含4个比较重要的内容,分别是:

1、m_ids:表示在生成readview时当前系统中活跃的读写事务的事务id列表 2、min_trx_id:m_ids中的最小值 3、max_trx_id:表示生成readview时系统中应该分配给下一个事务的id值 4、creator_trx_id:表示生成该ReadView的事务的事务id,默认值为0

这里需要补充重要的一点:事务执行过程中,只有在第一次真正修改记录时(比如使用INSERT、DELETE、UPDATE语句),才会被分配一个单独的事务id。

有了这四个概念和readview的概念,我们可以根据下面的方法来判断记录的某个版本是否可见:

第一条:如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。 第二条:如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。 第三条:如果被访问版本的trx_id属性值大于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。 第四条:如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。

这样说可能比较拗口,我们看下例子吧,假设现在有一个RC隔离级别的事务开始执行,事务id为20:

然后再开启一个事务id为30的事务,对别的表执行一个update操作,目的是为了生成版本号。

此时我们先把版本链画在这里:

这里需要注意的是蓝色部分的trx_id之所以为20,是因为在trx 20里面更新了两次name的值,而在trx 30中还没有对name的值进行更新。

此时我们在开启第3个事务,事务id为40,并在事务中执行select * from swordsman where id=1;

此时我们思考一下,这个事务读取到的值应该是多少?

分析过程如下:

1、这个select的语句会生成一个ReadView,其中m_ids的列表里面有事务id为20和30的两条记录,也就是m_ids[20,30],min_trx_id=20,max_trx_id=31(30的下一个事务id是31),creator_trx_id=0(默认值)

2、从版本链中,我们套用之前的四条规则,可以发现最新值是阳顶天,版本值是20,在m_ids里面,所以不符合规则当中的第四条。接着往下找

3、下一个版本的name列是杨逍,trx id是20,在m_ids里面,所以不符合第四条要求,继续跳到下一个版本

4、下一个版本的name值是"张无忌",trx id的值是10,符合可见性规则的第2条,10<20,说明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。

所以这个select的值就是trx id为10的那条记录,也就是: +----+-----------+-----------------+ | id | name | kongfu | +----+-----------+-----------------+ | 1 | 张无忌 | 乾坤大挪移 | +----+-----------+-----------------+

为了避免混乱,我们今天先写这么多,改天再谢谢RC隔离级别和RR隔离级别的区别,今天的实验是在RC隔离级别下的。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-09-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DBA随笔 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档