来源:mikechen的互联网架构
MVCC 是提高 MySQL 数据库性能和并发性能的关键。
MVCC 是一种强大的数据库并发控制机制,它可以解决并发访问数据库时的很多问题,例如死锁、快照读、读写阻塞等。
当出现读写冲突时,MVCC 在不加锁、或尽量减少锁使用的情况下,也能高效处理读写冲突,实现非阻塞并发读。

大家好,我是 mikechen。
本文主要详解 MVCC,MVCC 是数据库性能优化提升的关键,也是大厂面试高频,非常重要。为方便大家系统学习,我已将本文归纳到《MySQL面试题专项突击》PDF,约 20 万字,全部是大厂高频必考,拉到文末可自取。

注:MyIsam 不支持事务,本文围绕 InnoDB 引擎来讲。
MVCC 又称为多版本并发控制,英文全拼 Version Concurrency Control。
MVCC 为每一个事务创建多个数据版本,每个版本对应一个特定时间点的数据库状态。
不同事务之间,彼此不干扰,基于各自的时间点,进行读取和写入操作。
想要更好掌握 MVCC ,有必要先了解当前读、快照读。
当前读(Current Read)读取的是最新数据,而不是历史的数据。
加锁的 SELECT、或对数据进行增删改,都会进行当前读。

当前读常用于需要读取最新数据状态的场景。
例如,库存管理系统、实时监控系统等。
当前读的特点:
事务在读取数据时,每次都会读取当前的最新数据版本。
事务在读取数据时,不允许写入;在写入数据时,不允许其他事务读取。
当前读允许读取当前事务未提交的数据,在并发环境中,可能导致一致性问题。
快照读(Snapshot Read),又称为一致性读 。
快照读读取的是快照数据,不加锁的普通 SELECT ,都属于快照读。

快照读常用于需要事务隔离和数据一致性的场景。
例如,报表生成、数据分析和历史查询等。
快照读的特点:
在读取数据时,会读取创建事务时的数据版本,不受其他事务影响。
由于它只读取已提交的数据版本,确保了数据的一致性和隔离。
提供了事务开始时的数据一致性视图,避免了并发冲突和未提交数据的影响。
快照读和当前读的区别:
快照读:仅读取已提交的数据版本,不读取其他事务未提交的数据。
当前读:读取的是最新数据,而不是历史的数据。
MVCC 的作用是提高数据库的并发性能。
并发读-写时:读操作不阻塞写操作,写操作也不会阻塞读操作,避免了同一数据在不同事务之间的竞争。
并发访问数据库时:解决了幻读、不可重复读等事务隔离问题。
MVCC 可以适应不同的应用需求,因为它允许读未提交、读已提交、可重复读和串行化等不同的事务隔离级别。

由于数据库不允许多个事务同时写入相同的数据,故 脏写 不会出现。
我们来看下,三种常见的 MySQL 数据库并发场景:
读 - 读:通常不会存在任何问题,也不需要并发控制。
读 - 写:有线程安全问题,可能会造成脏读,幻读,不可重复读,需要 MVCC 控制。
写 - 写:有线程安全问题,可能会存在更新丢失问题,例如第一类更新丢失, 第二类更新丢失等。
对比下,
没有引入 MVCC 机制前
仅 读-读 可以并发执行,读-写、写-写 都会阻塞,并发性能较差。
引入 MVCC 机制后
只有 写-写 之间相互阻塞,其他操作都可以并行,并发性能较高。
MVCC 机制的实现核心是隐藏列、事务链 和 ReadView。
隐藏链
Innodb 为每行记录都添加了三个隐藏字段:row_id、trx_id、roll_pointer。
以确保数据的一致性和事务管理。

事务链
每次对记录进行 update 或者 delete 操作时,都会记录一条 undo log 信息。
每条 undo log 信息中,都有一个 roll_pointer 属性。
通过 roll_pointer ,将这些 undo 日志串成一个链表,构成数据的事务链,如下图所示:

由于一个记录会被一堆事务进行修改,在一个记录中,就会存在多个版本的 Undo log 信息。
那么,这么多个版本的Undo log 信息,事务应该看到哪个?
这里就要了解 ReadView 机制。
ReadView
ReadView,又称为读视图。
ReadView 是用于控制事务读取数据的逻辑视图,我们可以理解它为数据库中某一个时刻所有未提交事务的快照。
通过 ReadView ,可以确定一个事务能够看到哪些数据版本。
每个事务都会创建一个自己独有的 ReadView,记录当前活跃且未提交事务。
ReadView 的几个重要参数:

当事务在进行快照读时,会生成一个 ReadView 来进行可见性判断,去判断哪个版本的数据可读。
可见性判断是由可见性算法来确定的。
可见性算法的规则:

不同隔离级别下,生成 ReadView 的逻辑:
串行化不需要版本链,每次只有一个事务执行;读未提交则直接取版本链最新的版本数据就行了。
只有在读已提交(RC)和可重复读(RR)的隔离级别下,才会使用 MVCC 机制。
读已提交(RC)时:每次查询时,都会重新生成 ReadView。
可重复读(RR)时:在当前事务第一次查询时,生成 ReadView ,然后一直沿用,直至事务提交,以保证可重复读。
读已提交(RC) 和 可重复读(RR) 的区别是,生成 ReadView 的时机不同。
接下来,我们再来看一个具体的示例,加深理解。
假设有一张表,表中有姓名、性别、年龄:
CREATE TABLE `user` (`id` bigint NOT NULL COMMENT '主键',`name` varchar(20) DEFAULT NULL COMMENT '姓名',`sex` char(1) DEFAULT NULL COMMENT '性别',`age` varchar(10) DEFAULT NULL COMMENT '年龄',`url` varchar(40) DEFAULT NULL,PRIMARY KEY (`id`),KEY `suf_index_url` (`name`(3)) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
我们在表中插入数据:
INSERT INTO `user` (`id`, `name`, `sex`, `age`, `url`) VALUES ('1', 'mike', '1', '18', 'https://mikechen.cc');插入该记录的事务 id 为 60:

事务 id 为 80、120 的两条事务,现在要对这条记录进行 UPDATE 操作:

版本链在每次进行 update 操作时,将每次的操作详细记录在 undo log 中。
每条 undo log 中,都记录了 rol_pointer 信息,通过 roll_pointer 进行关联,可以构成数据的版本链。
版本链的头节点就是当前记录最新的值。
如图:

在各个版本中,包含了生成该版本时对应的事务 id。
利用这个记录的版本链来控制并发事务访问相同记录的行为,即 MVCC 机制。
本文重点介绍 MVCC 机制,包括 MVCC 的概念、作用、工作原理、实现示例等。
MVCC 是提高数据库性能和并发性能的关键,既是面试高频、也是必知必会,非常重要。