在各种系统中,凡是要使用什么,我们都要为其建立一个数据结构,借助数据结构,我们能更深入的了解。
MySQL中有几种非常重要的日志:
三种日志的配合使用,才能实现事务机制、数据恢复、主备同步等功能,是MySQL中非常重要的一个模块。
事务在提交时,先写入一个处于prepare的redo log,再写入一个binlog,最后再写入一个处于commit的redo log。
为什么需要两阶段提交?
两阶段提交是一个经典的分布式问题,当每个人都确认自己完成的时候,再一起提交。对于binlog和redo log来说,
若redo log提交完成,事务就不能回滚,否则可能会覆盖别的事务更新;但若redo log直接提交,binlog在写入的时候失败了,事务不能回滚,导致数据和binlog不一致。
WAL:Write Ahead Logging,核心思想就是在操作数据时,记录下操作数据的日志(redo log和binlog),并修改内存中对应的数据,此时该操作就完成了,在适当的时机,MySQL再将该数据更新到磁盘中。
WAL减少了磁盘写的次数,得益于两方面:
在binlog的数据结构中,XID、GTID、server_id以及记录的数据操作主体(由多个字段组成)是我们需要特别注意的几个字段。
XID:借助这个字段,binlog可以和redo log关联起来,在数据恢复时,配合使用。
崩溃恢复时,会按顺序扫描redo log。碰到既有prepare和commit状态的两个redo log,就直接提交;只碰到prepare状态、没有commit状态的单个redo log,拿着redo log的XID去寻找是否对应的binlog。
GTID:全局事务ID,是事务在提交时生成的唯一标识,可用于在从库中找新主库的同步位置、判断主备库是否有延迟。
server_id:记录了该操作第一次执行时所在的MySQL实例ID,可用于解决主备一致中循环复制的问题。
数据操作主体内容:binlog有row、statement、mixed三种记录数据操作的格式。
binlog的完整性保证:
binlog具有完整的格式,statement格式的binlog最后会有commit,row格式的binlog最后有XID event。
并且MySQL 5.6.2版本后引入了binlog-checksum参数,用于验证binlog的正确性。
statement:记录的是SQL语句的原文。这种方式占据空间小,但有可能会导致主备不一致。
row:记录了SQL语句操作行的所有字段,如delete语句就会记录所有字段的值,保证了主备库的操作都是一致的。但这种方式binlog占的空间会很大,比如我delete了10G的数据,那么binlog中就会记录10G的数据,也要耗费对应的IO资源。
mixed:statement和row格式混用,MySQL判断当前SQL语句是否会导致主备不一致,若是,则使用row,否则使用statement。
看似mixed方式很好,但是在数据恢复的时候,若binlog是row格式,直接解析binlog数据并应用其反逻辑即可,但有些SQL语句依赖上下文命令,若直接使用statement的binlog恢复,执行结果很有可能是错误的。并且,在主备同步的时候,使用row格式的binlog会使得主备数据不一致的问题更容易被发现。
所以,binlog的格式最好还是设置为row。
binlog是追加写,binlog文件写到一定大小之后会切换到下一个文件,不会覆盖之前的binlog。
写入过程:在事务的执行期间,先把binlog写入到binlog cache,等事务提交的时候,(write)再把binlog cache中的binlog写入到binlog文件(位于文件系统的page cache)中,清空binlog cache,并根据sync_binlog的设置,(fsync,此时才认为会消耗IO资源)将binlog持久化到磁盘中。
在进行数据恢复时,需要使用全量备份+binlog来恢复数据。数据恢复的流程:
redo log使得MySQL具有crash-safe能力,在数据库异常重启时,之前提交的记录也不会丢失。
崩溃恢复时,我们取出所有redo log:
若redo log里的事务完整,即有commit和prepare两个完整的redo log,那么直接提交该事务。
若redo log只有完整的redo log,判断binlog是否存在并完整:
- 若是,则直接提交事务,binlog已经写入了,会被从库使用,此时必须要提交事务,保证数据一致;
- 否则,利用undo log回滚事务。
redo log记录的是数据页上做出的修改,只有一种格式,但redo log有prepare、commit两个状态。
redo log是循环写,空间是固定的,wirte pos是当前记录的位置,checkpoint是当前要擦除记录的位置,擦除记录就是将记录更新到文件中。当write pos追上checkpoint时,代表redo log已经写满了,需要将记录更新到文件中,触发刷脏页flush。
redo log写满时,会停止库的所有更新操作,将redo log的checkpoint向前推进,以留出一定空间。根据所设置的脏页比例上限,确定要刷脏页的数量,全力刷脏页。
redo log的写入机制和binlog的类似,redo log有一个用于暂存redo log的redo log buffer,也具有write和fsync两个操作。redo log写入的策略由innodb_flush_log_at_trx_commit参数控制:
较binlog不同的是,innodb有一个每隔1秒就把redo log buffer中的所有日志(可能包含未提交的事务的redo log)write到文件系统的page cache中、然后fsync到磁盘中的后台线程。
LSN:日志逻辑序列号,对应redo log的写入点,每次写入长度为length的redo log,LSN的值都会加上length。
LSN和checkpoint组合起来,保证数据页不会被多次重复执行。
MySQL为减少写盘次数,引入了组提交机制(此时innodb_flush_log_at_trx_commit = 1)。当redo log buffer中的事务(该组的leader)要fsync时,会将小于当前LSN的所有redo log都fsync到磁盘中,其他组员就可以直接返回了。binlog也有这个机制。
在并发的情况下,第一个事务写完redo log buffer以后,该事务prepare的redo log fsync得越晚,组员就越多,节约IOPS的效果就越好。
因此,MySQL延迟了两阶段提交中redo log的fsync,两阶段提交的具体过程为:prepare的redo log write;binlog write;prepare的redo log write;binlog fsync;commit的redo log write。
每天记录更新的时候,都会同时记录一条回滚操作,借助undo log和记录的最新值,我们可以得到该记录的之前所处的状态。这也是MySQL中的多版本并发控制MVCC的基础,在可重复读RR的隔离级别中就需要用到。
直到不再有事务需要用到,即没有比这个undo log更早的事务视图时,undo log才会删除。而长事务意味着数据库中会存在一个很老的事务视图,所有它可能用到的undo log都要保留,这些undo log会占据大量空间。
当事务因为某种原因需要回滚时,就需要使用undo log来恢复。