抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

摘要:本文学习了InnoBD存储引擎独有的多版本并发控制机制。

环境

CentOS Linux release 7.6.1810
MySQL 5.7.40

1 简介

1.1 说明

在使用锁机制的情况下,只有读读支持并发,读写、写读、写写都会被阻塞,虽然解决了并发问题,但会导致性能严重下降。

为了在解决并发问题的同时兼顾数据库的性能,引入了MVCC机制,在不使用锁机制的情况下,也能解决读写和写读的并发问题。

MVCC机制的全称为Multi-Version Concurrency Control,即多版本并发控制,主要是为了提升数据库的并发性能,使用比锁机制更好的方式解决了读写和写读的并发问题,从而确保了任何时刻的读操作都是非阻塞的。

在MySQL数据库中只有InnoDB引擎实现了MVCC机制。

1.2 作用

在RC读已提交中,MVCC机制解决了写读的并发问题,即解决了脏读的问题。

在RR可重复读中,MVCC机制解决了读写的并发问题,即解决了不可重复读的问题。

2 原理

2.1 说明

在InnoDB引擎中,MVCC机制的实现主要靠表中的三个隐藏字段,以及Undo日志和Read视图实现的。

当前事务在执行查询语句时,会生成Read视图,根据Read视图判断查询当前表还是查询Undo日志,不同的事务生成不同的Read视图,可能查询当前表也可能查询Undo日志。

快照读和当前读:

  • 把通过Undo日志进行的查询操作称为快照读,无锁的查询操作有可能会执行快照读。
  • 把通过当前表进行的查询操作称为当前读,有锁的操作都会执行当前读。

2.2 概念

2.2.1 隐藏字段

在创建表时,除了创建显示定义的字段,还会默认创建隐藏字段:

  • trx_id:事务ID,记录最近修改和插入这条记录的事务ID,事务ID遵循递增机制。
  • roll_pointer:回滚指针,记录最近修改这条记录的上一版本,通过这个字段可以在Undo日志中找到最近的历史版本。
  • row_id:隐藏主键,如果数据表没有主键,InnoDB会在这个字段创建聚簇索引。
  • deleted_bit:删除标识,删除时将其标记为删除,事务提交后会有线程将标记为删除的数据彻底删除,事务回滚后恢复原值,避免频繁修改索引树的结构。

2.2.2 Undo日志

Undo日志可以看做一个版本链,用于保存修改和插入的历史版本,节点可以看做表中记录的拷贝,通过roll_pointer字段行成链式结构。

在执行快照读时,会根据roll_pointer遍历版本链,直到找到符合要求的历史版本。

2.2.3 Read视图

在执行查询操作时,MVCC会生成一个Read视图,可以看做一个事务快照,当前系统中活跃的事务对应的Read视图组成一个快照列表。

因为事务ID是按先后顺序生成的,所以比较要查询的记录的trx_id字段上的事务ID和Read视图列表中的事务ID即可判断数据的来源:

  • 如果trx_id小于Read视图列表的最小值,说明最近更新这条记录的事务发生在过去已提交,执行当前读。
  • 如果trx_id在Read视图列表范围中,需要进一步判断:
    • 如果在Read视图列表中能找到trx_id则表示事务未提交,执行快照读。
    • 如果在Read视图列表中找不到trx_id则表示事务已提交,执行当前读。
  • 如果trx_id大于Read视图列表的最小值,说明最近更新这条记录的事务发生在未来未提交,执行快照读。

2.3 分析

2.3.1 查询操作

执行查询操作时,生成Read视图,对比数据行上的trx_id字段,进行当前读或者快照读。

在快照读时,如果找到的历史版本的删除标志字段不为空,并且删除事务已提交,说明记录已被删除,不需要返回数据。

对于RC读已提交,同一个事务中的每次查询都会创建Read视图,解决更新时读取产生的脏读的问题,不能解决读取时更新产生的不可重复读的问题。

对于RR可重复读,同一个事务中只有第一次查询会创建Read视图,以后的查询都会使用已有的Read视图,解决读取时更新产生的不可重复读的问题。

2.3.2 插入操作

复制新纪录到Undo日志,当事务提交时删除此条Undo日志,当事务回滚时反向操作然后删除和事务有关的Undo日志。

插入记录,trx_id字段保存当前事务ID,roll_pointer字段为空。

2.3.3 修改操作

复制原记录到Undo日志。

修改记录,trx_id字段保存当前事务ID,roll_pointer字段保存Undo日志。

2.3.4 删除操作

复制原记录到Undo日志。

修改记录,trx_id字段保存当前事务ID,roll_pointer字段保存Undo日志,删除标志设为true。

2.4 总结

当一个事务尝试修改数据时,会将旧数据放入Undo日志中,用于实现多版本控制。

当一个事务尝试查询数据时,会创建Read视图,用于查看历史数据。

评论