什么是MVCC,一文搞懂MySQL的MVCC机制

MVCC是什么

MVCC,即Multi-Version Concurrency Control (多版本并发控制)。它是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。

数据库中同时存在多个版本的数据,并不是整个数据库的多个版本,而是某一条记录的多个版本同时存在,在某个事务对其进行操作的时候,需要查看这一条记录的隐藏列事务版本id,比对事务id并根据事物隔离级别去判断读取哪个版本的数据。

数据库隔离级别读已提交、可重复读 都是基于MVCC实现的,相对于加锁简单粗暴的方式,它用更好的方式去处理读写冲突,能有效提高数据库并发性能。

我们先回顾一下事务的相关概念,以便更好的理解mvcc的实现原理

MySQL的事务

redo log

redo log重做日志,是记录物理数据变化的日志,使用数据库DML对数据的修改操作,都会产生redo log,可以保证事务的持久性

undo log

undo log是回滚日志,有两个作用:提供回滚操作多行版本控制(MVCC)

在数据修改的时候,不仅记录了redo,还记录了相对应的undo,如果因为某些原因导致事务失败或回滚了,可以借助undo进行回滚,

undo log和redo log记录物理日志不一样,undo log主要记录的是数据的逻辑变化,它是逻辑日志

可以认为执行insert时会对应在undo log中记录一条delete语句,并且会记录这个版本的事务id(txid),执行update语句会有一条update语句来使之数据恢复到上个版本。

当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚

多行版本控制的时候,也是通过undo log来实现的

当读取的某一行被其它事务锁定时,它可以从undo log中分析出该行记录以前的数据是什么。从而提供该行版本信息。

并且undo log的内容变更也会记录到redo log中,从而实现undo log的持久化

总而言之就是undo log提供了一种数据库快照的功能,通过事务id和undo log我们可以找到历史版本的数据

MySQL事务的隔离级别

SQL标准中定义了四个隔离级别:

  • READ-UNCOMMITTED(读取未提交):最低的隔离级别,允许读取尚未提交的数据变更,可能导致脏读、幻读、不可重复读;
  • READ-COMMITTED(读取已提交):允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读任有可能发生
  • REPAATABLE-READ(可重复读):对同一字段的多次读取结果都是一致的,除非数据本身是被本身事务所修改的,可以阻止脏读和不可重复读,但幻读仍有可能发生
  • SETIALIZABLE(可串行化):最高的隔离级别,完全服从ACID原则,所有事务单个依次执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读,不可重复读以及幻读。

MVCC原理

事务版本号

事务每次开启前,都会从数据库获取一个自增长的事务ID,可以从事务ID判断事务的执行先后顺序

隐式字段

对于InnoDB存储引擎,每一行记录都有两个隐藏列trx_idroll_ptr,如果表中没有主键和非NULL唯一键时,则还会有第三个隐藏的主键列row_id

MySQL行记录中除了记录业务数据外,还有隐藏的 trx_idroll_ptr

  • trx_id:表示最近修改的事务的id,每次一个事务对某条聚集索引记录进行改动时,都会把该事务的事务id赋值给trx_id隐藏列,新增一个事务时,trx_id就会递增,因此trx_id能够表示事务开始的先后顺序
  • roll_ptr:指向上一个版本的地址,每次对某条聚集索引记录进行改动时,都会把旧版本写入undo log中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。

版本链

MySQL的每行记录逻辑上其实是一个链表

演示说明:


update user set name = '肉蟹宝1' where id = 1 

这个链表存在于undo log中,和最新版本的数据不在一起

每次更新后,都会将旧值放在一条undo log中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_ptr属性连接成一个链表,我们把这个链表称为版本链,版本链的头节点就是当前记录最新的值

另外,每个版本中还包含生成该版本时对应的事务id(trx_id)

ReadView

说完了undo log 和版本链,再来说说ReadView,前面我们说过MVCC只在read_commited和repeatable_read两个隔离级别下工作,而read_commited和repeatable_read的区别不同就在于它们生成的ReadView的策略不同

对于使用read_commited和repeatable_read隔离级别的事务来说,都必须保证读到已经提交了的事务修改过的记录,也就是说假如另一个事务已经修改了记录但未提交,是不能直接读取最新版本的记录的

核心问题就是:需要判断一下,版本链中哪个版本是当前事务可见的

为此InnoDB提出了一个ReadView的概念,这个ReadView中有一个id列表,trx_ids来存储系统中当前活跃着的读写事务,也就是begin了但是还未commit或rollback的事务

流程说明:

  1. 获取事务自己的版本号,即事务ID
  2. 获取Read View
  3. 查询得到的数据,然后Read View中的事务版本号进行比较。
  4. 如果不符合Read View的可见性规则, 即就需要Undo log中历史快照;
  5. 最后返回符合规则的数据

演示说明:

提交了trx_id是2的记录后,接着新建trx_id为3的事务,修改name的值,但是事务还没有提交

update user set name = '肉蟹宝 2 ' where id = 1 

则此时的版本链是:

显然,此时的trx_ids为[3]

如果另一个事务查询id为1的记录,因为trx_ids当前只有trx_id为3的事务,而trx_ids的意义是记录未完成的事务。在这里,事务未完成,所以该条记录不可见,继续查询下一条,结果返回肉蟹宝1

此时我把trx_id为3的事务提交了,并且新建了一个trx_id为4的事务,修改name,且不提交事务

update user set name = '肉蟹宝 3 ' where id = 1 

这时的版本链就是:

read-committed —— 每次查询数据前都生成一个 ReadView

trx_ids 将更新为[ 4 ],版本链通过 trx_id 对比查找到的结果就是肉蟹宝2

repeatable-read —— 在第一次查询数据时生成一个 ReadView,之后的读都复用之前的。

不会有重建的 ReadView , trx_ids 还是 [ 3 ],MySQL 认为事务3未完成,所以 select 的结果是肉蟹宝1。第2次 select 结果和第1次一样,所以叫可重复读。

小结

从上边的描述中我们可以看出来,所谓的MVCC--多版本并发控制指的就是在使用read-commited、repeatable-read这两种隔离级别的事务在执行select操作时,访问记录的版本链的过程。这样子就可以使不同的事务的读-写、写-读作并发执行,从而提升系统性能。

read-commited、repeatable-read这两个隔离级别的很大一个不同就是:生成ReadView的时机不同

read-commited 在每一次进行select操作时都会生成一个ReadView,而repeatable-read只在第一次进行普通select操作时生成一个ReadView,之后的查询操作都重复使用这个ReadView

参考与感谢:

深入理解MYSQL的MVCC机制

javaguide

猜你喜欢

转载自blog.csdn.net/qq_45779998/article/details/128453526