基本概念:

并发环境下的一种数据安全控制的方法,本质上就是一种乐观锁,用于实现读已提交(READ COMMITED)和 可重复读(REPEATABLE READ)

注:我们一般说的mvcc不是针对mysql的,不同的存储引擎对mvcc会有不同的实现,一般的mvcc都是针对mysql的innodb而言

以下的讨论只针对innodb

实现:

让每个不同的事务读取同一行的数据时,每一个事务修改这行数据是不同的版本,innodb只需要去记录这个数据的访问链,就可以实现

select的并发执行

实现原理:

MVCC利用多版本的思想,在MVCC中事务的所有写操作(create、update、delete)会为数据行新增一个最新的版本快照,而读操作会去读旧版本的快照。读操作和写操作是相互隔离的,没有依赖关系

Undo Log(回滚日志):

innodb存储引擎中,一个聚簇索引(主键索引)中有两个隐藏字段:trx_id 和roll_pointer.存放在b+树的叶子节点中

trx_id: 只要有任何一个事务对某条聚簇索引进行修改,该事务的id就会记录到这个字段里面

roll_pointer:当任意一个聚簇索引记录被修改,上一个版本的数据记录就会被写入Undo log日志中,那么这个roll_pointer就是存储了一个指针,这个指针指向这个聚簇索引订单上一个版本的记录位置,通过这个指针获取每一个历史版本的记录.

mysql可重复读的原理 mysql可重复读怎么实现的_数据

总结:

trx_id就是记录修改每条聚簇索引的事务id;roll_pointer就是一个指针,这个指针指向每一个历史操作版本的数据存储的地址;每一次修改操作都会生成一个Undo log版本,每个版本都是隔离的。

如果有事务C、事务D等一直对这个记录进行修改,那么这条记录的roll_pointer指针就会一直这样递归修改下去,最终形成一个关于修改和删除操作的版本链;(注:查询不会生成Undo log innode的版本链分为两种:insert Undo log update Undo log)

读操作和写操作是相互隔离的,写操作负责生成版本链,而读操作只需要根据规则去查看对应的某一个版本,然后读取就行了。这个规则就是read view

Read View:

read view 存放着一个列表,这个列表用来记录当前数据库系统中活跃的读写事务,也就是已经开启,正在进行数据操作但是还未提交保存的事务。可以通过这个列表来判断一个版本是否对当前事务可见。其中,有四个重要的字段:

creator_trx_id:创建当前read view所对应的事务id

m_ids: 所有当前未提交事务的事务id,也就是活跃事务的事务id列表

min_trx_id:m_ids里最小的事物id值

max_trx_id:InnoDB需要分配下一个事务id值(累计递增)

MVCC实现可重复读:

假设现在有AB两个事务对id=1的数据进行操作,A事务id20 B事务id30

mysql可重复读的原理 mysql可重复读怎么实现的_数据库_02

那么这两个事务创建的Read View:

mysql可重复读的原理 mysql可重复读怎么实现的_mysql可重复读的原理_03

此时事务A的creator_trx_id=20,事务B的creator_trx_id=30。

由于仅有两个活跃的事务,事务id分别为20和30。事务列表中最小的的事务id是事务A,所以min_trx_id应该为20,下一个也就是最大的事务id的max_trx_id值应该为事务B的下一个id,也就是31。

事务A去读取主键id为1的数据,找到了记录后就会去查看该记录的trx_id,事务A查看到该记录的trx_id值为10。

mysql可重复读的原理 mysql可重复读怎么实现的_mysql_04

随后和creator_trx_id进行比较:

mysql可重复读的原理 mysql可重复读怎么实现的_mysql_05

发现这个记录的trx_id=10 < 事务A.creator_trx_id=20,判断到记录的事务ID不存在活跃事务列表中并且小于事务A的事务ID,代表本次记录是在事务A之前提交的,可以放心读取,读取完之后,会将记录的trx_id修改为事务A的id。

mysql可重复读的原理 mysql可重复读怎么实现的_mysql_06

事务A将id=1的数据行中的age修改成50:

mysql可重复读的原理 mysql可重复读怎么实现的_数据_07

同时,Undo log的另外一个隐藏字段roll_pointer指针会指向事务A修改之前的的版本,就是age=10的地址,方便下次被查询:

mysql可重复读的原理 mysql可重复读怎么实现的_数据库_08

随后事务B也进来,对id=1的数据行进行update操作将age=50修改成60,同样的跟事务A一样的操作,随后将age修改成60;

重点:

过一会,事务A在去读取id=1的数据行,发现这个记录的trx_id已经变成30,需要再次进行值的区间比较。判断事务A.creator_trx_id=20 < 记录的trx_id=30 < max_trx_id = 31,并且trx_id=30存在m_ids列表中,代表自己读取是和自己同一时间范围内启动另外一个未提交的活跃事务修改的值。

那么此时事务A是不会去读取这个版本的记录,而是去通过Undo log中的roll_pointer指向的上一个版本的记录,直找到第一个trx小于自己的事务id并且不在活跃列表的记录,读取这个记录

这样就实现了可重复,每一个事务去读取或者修改同一个记录时,只能操作已经提交的记录,不能操作未提交事务的记录。

MVCC实现读已提交:

原理就是上面读取记录的过程

总结:

通过以上描述,我们就可以清楚的知道:InnoDB 中,MVCC 就是通过 Undo Log + Read View 进行数据读取,Undo Log 保存了历史快照,而 Read View 规则帮我们判断当前版本的数据是否可见。从而不需要通过加锁的方式,就可以实现提交读和可重复读这两种隔离级别。