Mysql系列--MVCC机制

Posted 低调扫地僧

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mysql系列--MVCC机制相关的知识,希望对你有一定的参考价值。

MVCC是Innodb存储引擎支持的,主要用来提高mysql的读写性能的。在中,我们了解到数据库的隔离级别有 未提交读、提交读(RC)、可重复读(RR)以及串行化。

目前MVCC机制主要适用于RC和RR两种隔离级别。


1、MVCC是什么?

MVCC全称为Multi-Version Concurrency Control,即多版本并发控制,是一种并发控制方法。在Mysql Innodb引擎中,MVCC主要用于提高数据库并发性能,使用一种更优的方式去处理读-写冲突,做到在读写冲突时,读不影响写,写也不影响读。同时,MVCC也解决了脏读、不可重复读和幻读的问题,但是不能解决更新丢失的问题。

在进行MVCC原理学习之前,我们需要先了解Mysql中的读:当前读和快照读。


2、当前读和快照读

2.1 当前读

就是读当前最新的内容,在读取的过程中,会对该记录进行加锁,阻止其它事务对其进行修改。以下操作都是当前读:select ... lock in share mode、select ... for update、update、insert、delete等。


2.2 快照读

就是读取的是快照内容,这个数据可能不是最新的,多个事务读取的快照内容也可能不一样(因为是不同时间段的快照),也不用对其进行加锁,其它事务可以读取并修改数据。以下操作是快照读:不加锁的select操作。快照读是基于MVCC实现的,目的是提高Mysql的并发性能。


MVCC本质上是为了实现读-写冲突不加锁,这里的读是指快照读,而不是当前读。在理论实现上就是维护一份数据多个版本,使得读-写操作没有冲突。下面我们来学习MVCC实现的具体原理。


3、MVCC原理
MVCC是多版本并发控制,大致原理如下:每次修改时都生成一个版本,每个版本都关联一个事务id,读操作只读取本次事务开始前最新的数据库快照。

基于以上原理,MVCC需要解决以下问题:版本维护、快照。

3.1 版本维护

对于每条记录,除了我们定义的字段外,还存在三个隐式字段:DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID

  • DB_TRX_ID:最近修改的事务id

  • DB_ROLL_PTR:回滚指针,指向该条记录的上一版本,存储在Undo log的存在rollback segment中,通过该指针可以形成一个版本链。

  • DB_ROW_ID:隐藏的自增ID(隐藏主键),如果没有设置主键,Mysql会自动使用该字段产生聚簇索引。


DB_ROLL_PTR字段主要是配合undo日志使用,Undo日志主要分为两种:Insert Undo日志和Update Undo日志。

对于Insert Undo日志,只在事务回滚时需要,事务提交后就可以被删除;对于Update Undo日志,不仅在事务回滚时需要,还在快照读时需要,只有快照读或回滚时不涉及该日志时才能被删除。


需要注意,Mysql在删除记录时,并不是直接删除记录,而是给该记录打个标,然后由Purge线程异步删除。


上图是修改记录的版本链,在修改时,会先将记录数据复制一份到Undo日志中,并修改当前记录的DB_ROLL_PTR指向刚才复制的数据,同时设置DB_TRX_ID(递增的)。在每个事务开始时,都会分配一个事务id(DB_TRX_ID),这个事务id是递增的,因此最新的事务其对应的事务id越大。


3.2 快照

在事务执行快照读之前,Innodb引擎会生成一份快照,此时如何“正确”读取数据呢?为了“正确”的读取数据,就不得不先了解Read View(读视图)。Read View是在事务进行快照读的那一刻产生数据库当前活跃事务的快照,主要用于判断数据可见性,即判断当前事务能看到什么版本的数据。


Read View通过可见性算法来判断事务数据的可见性,假设low_trx_id表示当前活跃事务的最小id,up_trx_id表示下一个即将分配的事务id,需要查看的记录行对应的事务id为db_trx_id,具体可见性算法如下:

  1. 如果db_trx_id < low_trx_id,说明当前事务是可以看到该事务db_trx_id对应的数据。

  2. 如果db_trx_id >= up_trx_id,说明事务db_trx_id是在Read View生成之后出现的,该事务db_trx_id对应的数据对当前事务是不可见的。

  3. 如果low_trx_id <= db_trx_id < up_trx_id,需要检查事务db_trx_id是否属于当前活跃的事务,如果属于,则说明当前事务还未提交,事务db_trx_id对应的数据对当前事务不可见;否则,事务db_trx_id对应数据对当前事务可见。

当事务db_trx_id对当前事务不可见时,需要根据DB_ROLL_PTR找到上一个事务,然后重复上面流程,直到找到可见的数据或者DB_ROLL_PTR为null为止。


下面以一个具体的例子进行说明,假设想查看DB_ROW_ID为1的记录数据Fdata,Read View为查看瞬间生成当前活跃事务对应视图。

假设当前事务id为97,此时可看到Fdata为bb,这是因为事务103属于当前活跃事务,因此不可见,需要判断其对应的上一事务99,99大于low_trx_id(94),且小于up_trx_id(104),同时不属于当前活跃事务,因此对当前事务来说是可见的,所以Fdata为bb。


需要注意的是,在RC和RR隔离级别下,Read View的生成时机不同,导致两者的快照读的结果不同。

  • 在RR隔离级别下,在首次快照读时生成快照和Read View,在其后的操作都将使用同一份Read View。

  • 在RC隔离级别下,在每次快照读时都会重新生成快照和Read View。这就是在RC隔离级别下,可以看到其他事务提交更新的原因。

以上是关于Mysql系列--MVCC机制的主要内容,如果未能解决你的问题,请参考以下文章

Python进阶篇:MySQL隔离级别详解

Python进阶篇:MySQL隔离级别详解

MySQL锁机制与MVCC原理--推荐阅读

MySQL的MVCC机制是什么?

MySQL——MySQL InnoDB的MVCC实现机制

MySQL——MySQL InnoDB的MVCC实现机制