可以视为行级锁的一个变种。它在很多情况下都避免了加锁操作,因此开销更低。不仅是 Mysql,包括 Oracle、PostgreSQL 等其他数据库都实现了各自的 MVCC,实现机制没有统一标准。MVCC 是 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。
加锁能解决多个事务同时执行时出现的并发一致性问题。在实际场景中读操作往往多于写操作,因此又引入了读写锁来避免不必要的加锁操作,例如读和读没有互斥关系。读写锁中读和写操作仍然是互斥的。注意: MYSQL 只有RC,RR级别才有MVCC。并且只有快照读的场景下讨论MVCC才有意义
保存数据在某个时间点的快照,写操作(DELETE、INSERT、UPDATE)更新最新的版本快照;而读操作去读旧版本快照,没有互斥关系。这一点和 CopyOnWrite 类似。
版本号 InnoDB 的 MVCC 实现是:在每行记录后面保存两个隐藏列,一个列保存行的创建时间,另一个列保存行的过期时间(这里的时间是指系统版本号)。每开始一个新事务,系统版本号会自动递增,事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
系统版本号 SYS_ID:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。
事务版本号 TRX_ID :事务开始时的系统版本号。
Undo 日志 MVCC 的多版本指的是多个版本的快照,快照存储在 Undo 日志中,该日志通过回滚指针 ROLL_PTR 把一个数据行的所有快照连接起来。
例如在 MySQL 创建一个表 t,包含主键 id 和一个字段 x。我们先插入一个数据行,然后对该数据行执行两次更新操作。
INSERT INTO t(id, x) VALUES(1, "a");
UPDATE t SET x="b" WHERE id=1;
UPDATE t SET x="c" WHERE id=1;
因为没有使用 START TRANSACTION 将上面的操作当成一个事务来执行
根据 MySQL 的 AUTOCOMMIT 机制,每个操作都会被当成一个事务来执行
所以上面的操作总共涉及到三个事务。快照中除了记录事务版本号 TRX_ID 和操作之外,还记录了一个 bit 的 DEL 字段,用于标记是否被删除。
INSERT、UPDATE、DELETE 操作会创建一个日志,并将事务版本号 TRX_ID 写入。DELETE 可以看成是一个特殊的 UPDATE,还会额外将 DEL 字段设置为 1。ReadView 读视图
MVCC 维护了一个一致性读视图 consistent read view ,主要包含了当前系统未提交的事务列表 TRX_IDs {TRX_ID_1, TRX_ID_2, ...},还有该列表的最小值 TRX_ID_MIN 和 TRX_ID_MAX。
这样,对于当前事务的启动瞬间来说,一个数据版本的 row trx_id,有以下几种可能:如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;如果落在黄色部分,那就包括两种情况
a. 若 row trx_id 在数组中,表示这个版本是由还没提交的事务生成的,不可见
b. 若 row trx_id 不在数组中,表示这个版本是已经提交了的事务生成的,可见