MySQL InnoDB事务隔离的实现

1.MySQL中的几种重要的日志

MySQL存储引擎与磁盘文件的执行原理大致如上图所示。图中除了表空间用来存储真实数据外,还有两种重要的日志,redo log和undo log,MySQL官方释义如下:

The redo log is a disk-based data structure used during crash recovery to correct data written by incomplete transactions. 

An undo log is a collection of undo log records associated with a single read-write transaction. An undo log record contains information about how to undo the latest change by a transaction to a clustered index record.  

大意为:redo log是一种基于磁盘的数据结构,它被用来恢复未完成的事务写入的数据使之正确;undo log 是一系列单个读/写事务undo log 记录的集合,一条undo log记录包含如何撤销对一条聚簇索引记录最新修改的信息。大白话说,redo log(重做日志)用于在系统发生错误时恢复未执行完成的事务的写操作,undo log用于在事务回滚时撤销对某条数据的修改,当然undo log还有其他非常重要的作用,本文将主要围绕undo log展开。

2.MVCC与事务的隔离级别

博文MySQL InnoDB事务隔离级别中简述了innoDB存储引擎中的四种隔离级别,其中读未提交与串行化的实现比较容易理解,读已提交与可重复读的实现比较复杂,需要依赖我们上文中提到的undo log。

1.undo log链

每条数据(上文提到的clustered record) 是有多个隐藏字段的,我们目前只关心其中的两个,trx_id和roll_pointer。每个事务都是有一个系统分配的全局唯一ID,trx_id值为最后一次修改这条数据的事务ID,roll_pointer指向事务提交之前的生成的undo log,例如一个ID为101的事务添加了一条置为A的记录,当前数据的状态如下图所示,当前数据行对应的unlog表达的含义为,如果Tx A 发生回滚,本行数据被恢复为null。

此时有一个事务ID为105的事务B把记录值改为B, 那么B事务在提交之前,会生成一条undo log记录,事务提交之后,trx_id会被修改为105,roll_pointer会指向B生成的undo log记录,如下图所示,意为若TxB发生回滚,本行数据被恢复为valueA。

此时又有一个事务ID为110的事务C把记录值改为C,C事务提交之前,同样会生成一条undo log记录,如下图所示。

2.Read View

 consistent read view,一致性读视图,为某一时刻事务系统(trx_sys)的快照,把快照创建时事务系统的状态记下来,之后所有的读操作都根据当前事务ID与快照中的事务系统状态做比较,判断数据对事务的可见性。

Read View中保存的trx_sys状态包括:

字段 说明
rw_trx_ids 当前read view创建时还在运行中的事务ID组
min_trx_id 最小事务ID,rw_trx_ids中的最小ID
max_trx_id 最大事务ID,当前系统中已经生成的最大事务ID+1,即下一个要生成的事务ID
creator_trx_id

创建read view的事务ID,即当前事务ID

MySQL ReadView类

数据的可见性规则:

1.如果一行数据的trx_id等于creator_trx_id,则表示这条数据是由当前事务修改的,可见;

2.如果一行数据的trx_id小于Read View中的min_trx_id,则表示这条数据是当前事务创建之前就提交了的,可见;

3.如果一行数据的trx_id大于等于Read View中的max_trx_id,则表示这条数据是当前事务创建之后提交的,不可见;

4.如果一行数据的trx_id大于等于min_trx_id或且小于max_trx_id,则要看trx_id是否包含在rw_trx_ids中,如果包含,表示read view创建时事务还未提交,则不可见,要沿着undo log链追溯更早的版本,按照上述规则判断;如果不包含,表示trx_id的事务已经提交,可见。

以上规则可以总结为:当前事务创建之前提交的修改,可见,当前事务创建之后提交的修改,不可见,如果不能判断与当前事务的先后顺序,则在rw_trx_ids数组中查看是否存在提交版本的trx_id,如果能找到,说明当前事务创建时修改数据的事务还在运行中,则查看当前数据更早的版本,直到找到当前事务创建时已经提交的数据版本。

举例说明:当前系统的状态如下图所示,虚线框的事务表示尚未提交,实线框的事务表示已提交,此时三个未提交事务的read view如下表所示

  rw_trx_ids min_trx_id max_trx_id min_trx_id creator_trx_id value
Trx C [101, 105, 110] 101 121 101 110 C
Trx B [101, 105] 101 120 101 105 B
Trx A [101] 101 119 101 101 A

 如果事务A没有发生任何修改,如下图

  rw_trx_ids min_trx_id max_trx_id min_trx_id creator_trx_id value
Trx C [101, 105, 110] 101 121 101 110 C
Trx B [101, 105] 101 120 101 105 B
Trx A [101] 101 119 101 101 Z

 3.RR和RC的实现

InnoDB的隔离级别是依赖Read View实现的(RU和Serializable不需要),区别在于Read View创建的时机,RR在当前事务下所有的读都是一致的,说明Read View是在事务开始时创建的,且整个事务仅生成一次;RC是在每次读取的时候生成Read View,所以能够看到事务开始之后,其他事务已经提交的数据。

3.undo log的清除

是否对数据库所有操作的undolog都会永久保留?答案是否,undolog本身也要占据一定的磁盘空间,当事务提交之后,数据就会持久化到磁盘上,此时undolog对提交的事务而言已经失去了意义,但此时还不能删除undo log,因为根据上述MVVC机制,当前的undolog有可能还会被其他的Read View使用,直到当前undolog不再被任何Read View使用时,该undolog即可以被清理。

具体的实现为,MySQL的purge线程会找到当前系统中创建最早的Read View,所有在该Read View创建之前提交的事务所做的变更的undolog都可以被清理。

猜你喜欢

转载自blog.csdn.net/zibaihe007/article/details/111302793