摘要:本文学习了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视图,用于查看历史数据。
条