Mysql神乎乎的MVCC到底该怎么理解
作者 |慕枫Java
来源 | 慕枫技术笔记(ID:lulideguang)
引言
MVCC即多版本并发控制主要是为了解决数据库中并发事务读写的一致性问题,那么多个事务并发执行的时候事务的隔离到底是怎么实现的呢?Mysql默认的RR隔离级别是怎样避免不可重复读的问题呢?我们好好来分析一下。
- 什么是undo log多版本链
- MVCC如何保证事务隔离
- 总结
一、什么是undo log多版本链
Mysql的MVCC机制,以undo log版本链为实现基础。因此要理解MVCC机制,我们得先分析下undo log版本链是个什么东东。Innodb存储引擎给每个数据表都添加了三个隐藏字段:DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID。
(1)DB_TRX_ID: 标记更新当前数据记录的transaction id,每处理一个事务,其值自动+1。
(2)DB_ROLL_PTR: 回滚指针,记录了最新一次修改该条记录的undo log,回滚的时候就通过这个指针找到undo log回滚。
(3)DB_ROW_ID: 当数据表没有指定主键时,数据库会自动以这个列来作为主键,生成聚集索引。
假设有如下的一条数据,此时这条数据对应的DB_TRX_ID为10,由于之前没有数据,因此回滚指针对应指向空地址。
每个事务都有自己的一个id,就像其身份证一样唯一标记该事务。当事务启动的时候,向Innodb存储引擎进行申请。假设此时如果打南边来了个事务A,它的事务id为12,事务A对表中的数据字段count进行修改,修改后该条数据对应的事务id为12,同时回滚指针指向实际的undo log回滚日志的地址。
此时打北边又来了个事务B,它的事务id为20,事务B将表中的数据字段count修改为21,对应数据的事务id变为20,回滚指针指向上一条undo log信息。如下图所示:
如果一直有事务进行数据的修改,那么就会形成一条由回滚指针串联的undo log多版本链条。
二、MVCC如何保证事务隔离
阐述完了undo log多版本链条原理,我们知道当一个事务A(事务id=12)更新数据后,当前数据的事务id变为12,同时回滚指针指向undo log数据。每次进行数据更新后,事务id就是修改数据的事务id,同时回滚指针指向回滚数据,最终形成undo log链。但是仅仅依靠undo log多版本链好像并不能实现事务并发执行时的相互隔离,因此我们需要一种机制可以利用undo log多版本链来实现事务隔离。这个机制就是ReadView。
Mysql执行事务的时候,会生成一个ReadView,其中会包含以下重要信息:
(1)m_ids:mysql中未提交的事务id集合;
(2)min_trx_id:集合中最小的事务id;
(3)max_trx_id,mysql下一个要生成的事务id,也就是事务id集合中最大的事务id加1;
(4)当前需要执行的事务id;
下面我们来说说Mysql的RR(Repeatable Read )隔离级别即可重复读,读取数据的事务,无论读多少次都是和第一次读取数据获得的值时一样的。我们一起来看下可重复读是如何通过MVCC实现的。 假设现在有事务A以及事务B两个事务,A事务需要读取数据,B事务需要修改数据。
当事务A需要读取数据时,开启ReadView。由于此时数据库活跃的事务为事务A以及事务B,那么对应的ReadView中m_ids={12,34},min_trx_id=12,max_trx_id=35,当前需要执行的事务id为12。此时事务A读取数据时,先判断当前的事务id为12,而数据中的事务id为11,小于当前事务A的id。说明当前读取的数据是在事务A开启之前提交的,因此可以正常进行数据读取。
如果此时事务B进行了数据修改,修改count为29。而事务A再次进行数据读取时,继续进行判断,发现当前数据对应的事务id为34比当前的查询事务要大,但是小于max_trx_id,同时在m_ids中。说明该事务id对应的事务和事务A属于并发执行事务,因此不能进行数据读取。则根据undo log版本链,往上寻找undo log信息。如果找到的事务id小于当前读取数据的id则证明此时的数据是在当前开启查询事务之前提交的,因此可以进行数据的查询。
那么另外一个问题又来了,RC级别又是如何实现的呢?所谓RC级别,就是当别人的事务提交后,你就可以读取到别人修改后的值。因此会发生不可重复读问题。当设置为事务级别为RC时,它每次发起数据查询(set session transaction isolation level read committed;)后,每次进行数据查询都会新开启一个新的ReadView。
假设当前事务中活跃着两个事务,他们的事务id分别是12、34。此时事务id为34的事务更新了数据。此时数据更新为29。同时数据对应的事务id更新为34,同时回滚指针指向上一条数据。若此时事务id为15的事务进行数据查询,此时开启readview,进行检查,发现此时的数据中对应的事务id在活跃事务id中,说明是和查询事务差不多时机执行的,但是此时的事务还未提交。因此此时的数据不可以读,所以顺着undolog版本链条读取上一次的数据。同时进行判断。
如果事务B进行了提交,那么事务A再次进行数据查询的时候,会新开启一个ReadView,我们暂且称之为ReadViewA1,由于此时的事务B已经提交,所以ReadViewA1中对应的活跃列表中只有事务A对应的事务id为12。此时发现事务已提交,不再活跃事务列表中,因此可以进行数据读取。
综上分析,这就是Mysql通过ReadView以及undo log多版本链条实现RC以及RR的秘密。RC以及RR的区别就在于,RC事务隔离级别下,每次获取数据的时候,都会重新生成新的ReadView,再根据ReadView中的信心进行数据读取的判断。
三、总结
综合以上的分析我们大致理清楚了Mysql多事务并发的隔离原理,其本质是通过MVCC机制来进行实现的。而MVCC其实是基于ReadView以及undo log多版本链条实现。Mysql的默认隔离级别是RR,避免了脏读、不可重复读以及幻读的问题。