MySQL可以恢复到半个月内任意一秒的状态.
mysql> create table T(ID int primary key, c int);
这个表有一个主键ID和一个整型字段c,若要将ID=2这一行的值加1
mysql> update T set c=c+1 where ID=2;
首先执行语句前连接器要连接数据库,随后一个表上有更新时,跟这个表有关的查询缓存会失效,所以将表T上所有缓存结果都清空.分析器通过词法和语法解析直到这是一条更新语句,优化器决定使用ID这个索引,执行器负责具体执行,找到这一行,然后更新.
与查询流程不一样,更新流程还涉及两个重要的日志模块:redo log(重做日志)
和binlog(归档日志)
.
若每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程IO成本、查找成本都很高.
WAL(Write-Ahead Logging)技术即先写日志,再些磁盘. 当有一条记录需要更新时,InnoDB引擎就会将记录先写到redo log
并更新内存,此时更新就算完成了,同时引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做.
InnoDB的redo log
的大小是固定的,比如可以配置为一组4个文件,每个文件的大小是1GB,则共可以记录4GB的操作,从头开始写,写到末尾又回到开头循环写
write pos
是当前记录的位置,一边写一边后移,checkpoint
是当前要擦除的位置,也是往后推移并循环的,擦除记录前要将记录更新到数据文件.
若write pos
追上checkpoint
则不能执行新的更新,要先擦掉一些记录,将checkpoint
推进一下.
InnoDB通过redo log
可以保证即使数据库发生异常重启,之前提交的记录也不会丢失,称为crash-safe
.
redo log
是InnoDB引擎特有的日志,而Server层也有自己的日志,称为binlog
.
两者有如下不同:
redo log
是InnoDB引擎特有的,binlog
是MySQl的Server层实现的,所有引擎都可以使用.redo log
是物理日志,记录的是在某个数据页上做了什么修改,而binlog
是逻辑日志,记录的是语句的原始逻辑,如’给ID=2行的c字段加1’.redo log
是循环写的,空间固定会用完,binlog
是可以追加写入的,追加写是指binlog
文件写到一定大小后会切换值下一个,并不会覆盖以前的日志.redo log
里,此时redo log
处于prepare状态,然后告知执行器执行完成了,随时可以提交事务.binlog
,并将binlog
写入磁盘.redo log
改成提交状态,更新完成.将redo log
的写入拆成了两个步骤,prepare
和commit
,即两阶段提交.
如何将数据库恢复至半个月内任意一秒的状态
binlog
会记录所有逻辑操作,并且采用追加写
的形式,如果DBA承诺半个月内可以恢复,则备份系统中一定会保存最近半个月的所有binlog
,同时系统会定期做整库备份.
当需要恢复到指定的某一秒时,可以这样做:
binlog
依次取出来,重放到中午误删表的那个时刻.这样临时库和误删之前的线上库一样了,然后可以将表数据从临时库取出来,按需恢复到线上库.
为了让两份日志之间的逻辑一致.
由于redo log
和binlog
是两个独立的逻辑,若不用两阶段提交,要么就是先写完redo log
再写binlog
,或采用反过来的顺序.
假设当前ID=2的行,字段c的值是0,再假设执行update语句过程中,在写完第一个日志后,第二个日志还没有写完期间就发生了crash.
假设redo log
写完,binlog
还没有写完时,MySQL进程异常重启,根据redo log
,即使系统崩溃,仍然可以将数据恢复过来,所以恢复后c的值为1.
但是由于binlog
没写完就crash了,这时候binlog
里面就没有记录这个语句,因此之后备份日志的时候,存起来的binlog
里面就没有这条语句.
如果用binlog
来恢复临时库的话,恢复出来的库与原库的值不同.
如果再binlog
写完之后crash,由于redo log
还没写,崩溃恢复以后这个事务无效,所以这一行的值为0,但binlog
里已经记录了将c从0改为1
,若用binlog
恢复临时库,与原库值不同.
redo log
和binlog
都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致.