MVCC

MVCC是Multi-Version Concurrency Control「多版本并发控制」的缩写。
MVVC的思想就是保存数据的历史版本,通过对数据行的多个版本管理来实现数据库的并发控制。这样我们就可以通过比较版本号决定数据是否显示出来,读取数据的时候不需要加锁也可以保证事务的隔离效果。

MVCC的作用

  1. 读写之间阻塞的问题

通过MVCC可以让读写互不阻塞,可以提升事务并发处理能力。

  1. 降低了死锁的概率

因为InnoDB的MVCC采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行。

  1. 解决一致性读的问题

一致性读也称之为快照读,当我们查询数据库在某个时间点的快照,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果。

快照读和当前读

快照读「SnapShot Read」是一种一致性不加锁的读,是InnoDB并发高的核心原因之一。

  1. 一致性:指事务读取到的数据,要么是事务开始前就已经存在的数据,要么是事务自身插入或者修改过的数据。

不加锁的简单SELECT都属于快照读,例如:

  1. select * from t where id = 1;

当前读就是读取最新数据,而不是历史版本,加锁的SELECT就属于当前读,例如:

  1. SELECT * FROM T WHERE id = 1 LOCK IN SHARE MODE;
  2. SELECT * FROM T WHERE id = 1 FOR UPDATE;

InnoDB的MVCC如何工作

InnoDB如何存储记录的多个版本

事务版本号

每开启一个事务,我们都会从数据库中获取一个事务ID(事务版本号),这个事务ID是自增长的,通过ID大小,我们就可以判断事务的时间顺序。

行记录的隐藏列

InnoDB的叶子节点存储了数据页,数据页中保存了行记录,而在行记录中有一些重要的隐藏字段:

  • DB_ROW_ID:6-byte,隐藏的行ID,用来生成默认聚集索引。如果我们创建数据表的时候没有指定聚集索引,这时InnoDB就会用这个隐藏ID来创建聚集索引。采用的聚集索引的方式可以提升数据的查询效率。
  • DB_TRX_ID:6-byte,操作这个数据的事务ID,也就是最后一个对该数据进行插入或更新的事务ID。
  • DB_ROLL_PTR:7-byte,回滚指针,也就是指向这个记录的回滚日志的信息。

    回滚日志

    InnoDB将行记录快照保存在了回滚日志里,我们可以在回滚段中找到它们,如下图所示:
    image.png
    从图中能看到回滚指针将数据行的所有快照记录都通过链表的结构串联了起来,每个快照记录都保存了当时的db_trx_id,也就是那个时间点操作这个数据的事务ID。这样如果我们想要找历史快照,就可以通过遍历回滚指针的方式进行查找。

    可重复读隔离级别下,InnoDB的MVCC是如何工作的

    查询

    InnoDB会根据以下两个条件检查每行记录:
  1. InnoDB中查找版本早于当前事务版本的数据行(即行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改的;
  2. 行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。

只有符合上述两个条件的记录,才能返回作为查询结果。

插入

InnoDB为新插入的每一行保存当前系统版本号作为行版本号。

删除

InnoDB为删除的每一行保存当前系统版本号作为删除表示。
删除在内部被视为更新,行中的一个特殊位会被设置为已删除。

更新

InnoDB为插入一行新记录,保存当前系统版本号为行版本号,同时保存当前系统版本号到原来的行作为行删除标记。

总结

MVCC在一定程度上实现了读写并发,它只在 可重复读 和 读已提交 两个隔离级别下工作。其他两个隔离级别都和 MVCC 不兼容,因为读未提交,总是读取最新的数据行,而不是符合当前事务版本的数据行。而串行化则会对所有读取的行都加锁。
行锁,并发,事务回滚等多种特性都和MVCC相关