06UndoLog与MVCC

当事务出错,需要回滚时,怎么办?MySQL提供了undo 重做日志。

对于MySQL的一条记录有几个隐藏字段,如下图:


如果表没有主键,会默认生成一个row_id,一般表都有主键。

另外两个 trx_id 事务id,表示修改这条记录的事务。 roll_pointer, 该记录的上一条信息,指向的就是一个undo log记录。

最终一个记录会有一串历

史数据,对应不同的事务id,这就是Mysql的MVCC,多版本并发控制的实现原理。

当数据回滚时,只要根据roll_pointer,就可以回滚到历史版本了。

MVCC的实现

我们知道事务的隔离级别,有下面四个。

READ UNCOMMITTED:未提交读。

READ COMMITTED:已提交读。

REPEATABLE READ:可重复读。

SERIALIZABLE:可串行化。

对应的问题如下:


MySQL默认 RR级别,对于一般数据库,会出现幻读,比如事务1执行查询 id >2 的数据,然后事务2插入一个id=10的记录,提交了。  事务1在次执行就有可能看到这条记录,不过MySQL中解决了这个问题。很神奇吧,MySQL是如何实现的呢?


每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表,所以现在的情况就像下图一样:


为了实现隔离级别,MySQL提出ReadView的概念。

ReadView中主要包含4个比较重要的内容:

m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。

min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值。

max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。

小贴士: 注意max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4。

creator_trx_id:表示生成该ReadView的事务的事务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时生成该版本的事务已经被提交,该版本可以被访问。


对于不同的隔离级别,ReadView的工作机制如下:

ReadCommit 级别 读已提交

READ COMMITTED —— 每次读取数据前都生成一个ReadView

事务1,查询

事务2 id 200 执行插入,然后提交。

事务1 继续查询。


事务1在执行SELECT语句时会先生成一个ReadView,ReadView的m_ids列表的内容就是[200],min_trx_id为200,max_trx_id为201,creator_trx_id为0。

对于select语句,是不生成事务id的,所以creator_trx_id为0。

第一次查询, 事务1 2 都在

事务id因为0,readview中

m_ids:200

min_trx_id:200

max_trx_id:201

creator_trx_id:0

第一次查询还没有事务200的数据,也看不到。第二次执行,生成新的ReadView

事务1的readview中

m_ids:[]

min_trx_id:[]

max_trx_id:201

creator_trx_id:0

没有活跃的事务id,所以也查不出来。

对于RR的级别也是一样,ReadView以第一次为准,事务200提交的也看不到。

如果事务1插入 id为100,提交,事务2插入后 id为200,有了事物id,两次查询,RC与RR就不一样了。

RC下,事务200可以读取100的。

对于RR级别,以第一次的RV为准,所以即使100提交了, 100在 事务id中,还是看不到,这就避免了幻读。

幻读现象  

但是有一种情况,就是事务2提交数据后,事务1修改了这条数据,这时候这条数据的事务id就是事务1的id,就能看到了。

还有一种情况,事务1执行了时,事务2插入id=1提交,事务1也插入,报错。但是事务1怎么查却查不出来。

解决方式,select for update.

https://segmentfault.com/a/1190000016566788?utm_source=tag-newest

你可能感兴趣的:(06UndoLog与MVCC)