一、日志模块:redolog
redolog是一个空间有限的循环使用的数据修改日志。设想以下场景:在一个时间段内,频繁的对数据进行更新。
我们知道,为了数据的持久化,数据是被设计保存到磁盘上的,如果每一个数据的更新都需要更新到磁盘上,由于磁盘I/O的特性,将会有极大的性能开销。因此,MySQL在更新数据时,会首先写入内存,然后往磁盘上写入一个标记,表示当前更新已经结束,等空闲时才会把更新的内容写入到磁盘。而这个标记内容就存放于redolog日志文件中。redolog是一个大小有限的日志文件,当空间被占满时,MySQL会强制将一部分内容写入到磁盘中,然后擦除掉这部分记录用于后续的新的日志记录写入。
二、日志模块:binlog
MySQL作为一个程序,必然存在因误操作等原因导致的数据错误的情况,因此在某些时刻,我们需要进行数据还原。既然要还原数据,那么肯定需要一个日志,这个日志记录了MySQL对于数据更新的每一次操作。理论上根据某一个时间点的数据库数据,执行当前时间点之后的操作日志记录,则可以恢复相应的数据库数据。因此,binlog被设计而出,这个日志文件是增量写入的,当一个文件占满,则新建一个文件继续写入。
三、写数据的主要流程
通过对上述日志的简要了解,接下来着重分析写入数据时,MySQL的执行细节。流程如下:
上述流程默认处于一个事务中,重点关注最后三步:这三步也被称做为二阶段提交。
一、为什么需要二阶段提交
设想以下2种不存在二阶段提交的情况会发生什么。
-
先写redolog再写binlog
假如写完redolog,此时数据库崩溃。当数据库重启时,根据redolog恢复的数据是写操作之后的数据。而通过binlog还原的数据是写操作之前的数据,因此数据不一致。
-
先写binlog再写redolog
假如写完binlog,数据库崩溃,情况和上面的恰好相反,最终都是数据不一致。
二、二阶段提交是如何避开数据不一致的异常
设想以下几种数据库崩溃情况。
-
写入prepare之前崩溃
此时写操作失败,无任何影响
-
写入prepare后,写入binlog前崩溃
数据库重启后,先读取redolog日志,这个日志和binlog日志有一个关联字段XID,如果当前读取日志的事务是prepare状态,则会去读binlog日志,发现这个日志里面没有对应的完整事务,则事务回滚,所以判定写操作失败。
-
写入binlog后,写入commit前崩溃
数据库重启后的操作步骤同上,但此时在binlog之中发现对应的事务是完整的,标志当前事务是已经完成的,因此会自动提交事务,判定写操作成功。
-
写入commit后崩溃
数据库启动后直接读取到最新的commit的事务,无任何影响。
四、总结
简单来说,数据库为了保证数据的安全性,是通过2个关键日志来完成的。redolog记录了事务的状态,MySQL启动后会自动还原到最新提交事务那一刻的状态,binlog记录了数据库的一系列的操作。它们通过一个关联字段XID联系起来,当数据库重启时,就是通过这2个日志文件上的XID字段来判断某一个事务是否是已经完成的,如果完成,则自动提交。