公众号:慕枫技术笔记
真正的大师永远怀着一颗学徒的心
引言
MVCC
即多版本并发控制主要是为了解决数据库中并发事务读写的一致性问题,那么多个事务并发执行的时候事务的隔离到底是怎么实现的呢?Mysql
默认的RR隔离级别是怎样避免不可重复读的问题呢?我们好好来分析一下。
什么是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
,避免了脏读、不可重复读以及幻读的问题。
欢迎大家关注作者微信公众号:慕枫技术笔记,不定期更新文章,分享大厂面试经验以及技术交流,一起加入技术乌托邦吧