原创 启明南公众号
先说下MySQL的四个隔离级别:读未提交(RU)、读已提交(RC)、可重读(RR)、串行化(Serializable),本篇文章重点讲解可重读级别下的事务细节如:MVCC、视图的创建时机、版本链、Read VIew、读写底层实现。
RU、RC……这四个简称大家记一下,文章后面用的都是简称。
本篇文章的前提是隐式提交是开启的,即antocommit=1。
先看个例子,大家先自己思考下答案以及为什么会这样
CREATE TABLE `test` ( `id` int(11) NOT NULL, `val` int(11) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB;insert into test values(1,1),(2,2);
可能有三种答案吧:(1、2),(2、3),(1、3),如果我告诉你正确答案是1、3,你会不会觉得不可思议。为什么答案是这样呢?往后看。
MVCC
MySQL的并发事务会存在三个问题:脏读、不可重读、幻读。这三个现象不太了解的同学自行补充。
MySQL在RR隔离级别下是不会产生脏读、不可重读现象的,是怎么做到的呢?就是MVCC,即并发版本控制。想了解MVCC的细节,往后看。
创建快照的时机
MySQL中有三种开启事务的方式:
beginstart transactionstart transaction with consistent snapshot
这三种方式有什么区别呢?创建视图的时机不同。前两种方式,MySQL执行后不会马上创建视图,是在执行第一条select语句时才会创建,第三种方式MySQL执行后会马上创建视图。
什么是视图,即Read View。有的地方说在RR隔离级别下MySQL开启事务会创建快照,这个快照就是Read View。关于视图的细节,往后看。
MVCC实现基础
MVCC是依赖于表的两个隐藏列实现的,也可以说三个,因为如果一个表未设置主键索引,Innodb引擎会自动生成一个隐藏主键。
DATA_TRX_ID:最近更新这条数据的事务ID,占6字DATA_ROLL_PTR:存放指向上一个事务版本的指针,占7字节(版本链依赖于这个字段)DB_ROW_ID:自动生成的隐藏主键,占6字节(只有在一个表未设置主键时才会生成)
版本链
很多人把版本链这个东西想得过于神秘,它其实就是一个单链表。它的存在有两个目的:1、用于事务的回滚;2、用于多版本控制,配合Read View实现事务的隔离。
那版本链中的每个节点数据是从哪来的呢?一条数据被增删改操作后都会生成一条记录存放在undo log中,这里注意下,查不会生成记录。
有时候我们执行sql语句时没有显示地开启事务,但是在MySQL中增删改操作都会视为一次事务操作,操作后都会在undo log中生成一条记录,这个记录主要用于多版本控制,没有回滚一说。
下图就是文章开头的案例执行后生成的版本链。版本链这个东西不是真实存在的,它是由表中的隐藏字段DATA_ROLL_PTR及undo log动态计算出来的。
Read View
1、是什么
我们知道当隔离级别为RR时,是不会出现不可重读现象的。那底层是如何实现的呢?其实秘密就是Read View。
2、Read View中四个重要的属性
m_ids:保存在生成Read View时当前系统中所有活跃的事务的ID
min_trx_id:保存在生成Read View时当前系统中活跃的最小的事务的ID
max_trx_id:保存在生成Read View时分配给下一个事务的ID
creator_trx_id:保存生成该Read View的事务的ID
3、举例说明RV的四个属性
事务11启动时这四个属性的值:m_ids=[11],min_trx_id=11,max_trx_id=12,creator_trx_id=10
事务12启动后这四个属性的值:m_ids=[11,12],min_trx_id=11,max_trx_id=13,creator_trx_id=10
4、RV的四个属性的用途
用select语句查询时数据的可见性就是通过这四个属性+版本链计算出来的,都是从最新版本开始计算,计算规则:
如果被访问版本的事务ID与RV的creator_trx_id相同,意味着此时在查询自己修改过的记录,必然是可见的如果被访问版本的事务ID小于RV的min_trx_id,说明生成该版本的事务在当前事务生成RV前已提交,这个版本对当前事务就是可以的如果被访问版本的事务ID大于RV的max_trx_id,说明生成该版本的事务在当前事务生成RV后才开启,这个版本对当前事务就是不可见的如果被访问版本的事务ID在RV的min_trx_id与max_trx_id之间,那就需要做二次判断,判断被访问版本的事务ID是否在m_ids中,如果在,说明该事物是活动事务,即还未提交,那这个版本就不可见,如果不在,说明该事物已执行结束,那这个版本就是可见的
解析事务12
关于事务12的结果为什么是3不是2,就得搞清楚MySQL是如何执行update的。
其实我问一个问题,大家就想通了:update的时候是update视图还是表?肯定是表对不对?事实也确实是这样:update数据都是先读再写,update后会生成新的版本。这个读称为当前读,即有视图的情况下依然读表。
所以事务12执行update的时候会先去读表,读到的数据是2,然后执行加1后写回去。
后面select的时候就是MySQL的读逻辑了。
解析事务13
这边虽然也有分歧,但是不是很大,那我们联系Read View即版本链来分析下事务13的执行结果。
事务13在开启事务的时候会马上创建Read View,这个视图中id=1的数据行val=1,。事务12是在事务11后创建的,而且在事务13去读的时候事务还没提交,所以事务12的修改对事务11是不可见的。
事务13虽然没有明确开启事务的语句,但是MySQL底层执行sql语句都是以事务的方式去执行的,只不过采用的是隐式提交。什么是隐式提交?就是autocommit=1时,执行一条sql语句后MySQL自动提交了。虽然自动提交了,但是13大于事务11的max_trx_id,所以在版本链中生成的版本对于事务13也是不可见的。
关于Read View+版本链判断版本可见性的逻辑,大家一定要深刻理解,熟练使用。这不止对DBA,我觉得对于任何一个开发人员都是基础。
结语
至此就讲MySQL的事务讲明白了。当然这只是站在我的立场,我也不知道大家在看这篇文章的时候会由哪些问题,或者在MySQL的事务上还有哪些盲区,欢迎大家留意提问。
这篇文章核心讲的是可重读(RR)隔离级别下的事务,其他三个级别下的事务大家可以按照这个思路自行研究,后面我也会抽空写文章分享。但是学习是自己的事情,独立思考、钻研是很重要的品质,希望大家不要错过锻炼的机会。
想看更多文章的可以微信搜索公众号【Java晋升之路】,每天都会有精彩文章发布哟