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 |
数据的可见性规则:
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都可以被清理。