MySQL的逻辑架构图
MySQL中两个重要的日志模块:redo log(重做日志)和binlog(归档日志)
我们先来看redo log:
介绍一个MySQL里经常说到的WAL技术,即Write-Ahead-Logging,它的关键点就是先写日志,再写磁盘。具体来说,当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log里面,并更新内存,这个时候更新就算完成了,同时,InnoDB引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做的。
InnoDB的redo log是固定大小的,比如可以配置为一组4个文件,每个文件在大小是1GB,总共就可以记录4GB的操作。从头开始写,写到末尾就又回到开头循环写,如下面所示
write pos是当前记录的位置,一边写一边后移,写到第3号文件末尾就回到0号文件开头。checkpoint是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。write pos和checkpoint之间是还空着的部分,可以用户来记录新的操作。如果write pos追上checkpoint,就表示满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把checkpoint推进一下。
有了redo log,InnoDB就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。
再来看下binlog:
MySQL整体来看,其实就两块:一块是Server层,它主要做的是MySQL功能层面的事情;还有一块是引擎层,负责存储相关的具体事宜。redo log是InnoDB引擎特有的日志,而Server层也有自己的日志,称为binlog。
redo log和binlog的不同点有三个:
1)redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用;
2)redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给ID=2这一行的c字段加1”;
3)redo log是循环写的,空间固定,会用完;binlog是可以追加写入的。“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
一个update语句的执行流程,如下图所示
再介绍一下两阶段提交(指的是redo log的prepare和commit两个阶段)
为什么需要“两阶段提交”呢,这是为了让两份日志之间的逻辑一致。如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。简单说,redo log和binlog都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。
现在来回答标题中的问题:MySQL是如何做到可以恢复到半个月内任意一秒的状态的?这里需要有两个前提,即开启了定期整库备份和开启了binlog记录。恢复时的操作步骤如下:
1)首先,找到最近一次的全量备份,将这个备份恢复到临时库;
2)然后,从备份的时间点开始,将备份的binlog依次取出来,重放到我们期望的那个时刻。
binlog还有一种用途:当需要扩容的时候,也就是需要再多搭建一些备库来增加系统的读能力的时候,现在常见的做法也是用全量备份加上应用binlog来实现的。
注:
1)为了保证MySQL异常重启之后redo log的数据不丢失,强烈建议将innodb_flush_log_at_trx_commit这个参数设置成1,表示每次事务的redo log都直接持久化到磁盘;
2)为了保证MySQL异常重启之后binlog的数据不丢失,强烈建议将sync_binlog这个参数设置成1,表示每次事务的binlog都持久化到磁盘。