我们都知道InnoDB的事务由ACID四个特性,其中A原子性由Undo Log来保证,持久性由Redo Log来保证。本片文章记录了Redo Log和Undo Log,以及数据更新流程中Redo Log的两段提交的学习笔记。
Redo Log
Redo Log是物理日志。当发生数据修改时,InnoDB引擎会先将数据写入Redo Log中,并更新内存,此时更新就算完成。同时InnoDB会在合适的时机将记录异写到磁盘。写入的过程如下图:
为了确保每次日志都能写入到事务日志文件中,每次Log Buffer写如文件的过程都会调用操作系统的fsync()。因为MySQL工作在用户空间,Log Buffer处于用户空间内存中。若要写入磁盘中,还要经过内科空间的OS Buffer。此时fsync()就是将日志从OS Buffer写到磁盘的Log File中。
MySQL支持自定义在commit时将日志从Buffer Pool写到Log File中。可通过innodb_flush_log_at_trx_commit来配置,该变量有0、1和2三种,默认为1。具体流程如下图:
**0:**事务每次提交,会将内存中日志先写到Log Buffer,再写入OS Buffer,最后每秒异写磁盘;
**1:**事务每次提交,内存中日志直接写入磁盘;
**2:**事务每次提交,先写入OS Buffer,然后每秒异写到磁盘。
如果从安全角度来考虑,配置为1的方式是最安全的,因为每次commit都会直接异写到磁盘,但是IO太多,效率低。
如果使用配置为0或2,性能高但不安全。在0和2中,建议选择2,因为0比2要多义词数据拷贝过程。
关于Redo Log的循环写,假设有这样一组四块儿文件,write pos是当前记录的位置,写完向后移,从ib_logfile_3写到ib_logfile_0。checkpoint是当前删除的位置,也会向后循环删除,当然删除前要先将记录更新到数据文件。
Undo Log
Undo Log是为了实现事务的原子性,在MySQL数据库的InnoDB存储引擎中,还用Undo Log来实现多版本并发控制。
在操作任何数据之前,首先将数据备份到Undo Log中,然后进行数据修改。如果出现了错误,或用户执行了Rollback语句,系统可以利用Undo Log中的数据恢复到事务开始之前的状态。
Undo Log是逻辑日志,可以理解为:
当delete一条记录时,Undo Log中会记录一条对应的insert记录;
当insert一条记录时,Undo Log会记录一条对应的delete记录;
当update一条记录时,Undo Log会记录一条与之相反的update记录。
Binlog
与Redo Log和Undo Log数据InnoDB不同,Binlog是server层的日志,主要做MySQL功能层面的事。与Redo Log日志的区别如下:
Redo Log是InnoDB独有的,Binlog是所有引擎都可以使用的;
Redo Log是物理日志,记录的是在某个数据页上做了什么修改,Binlog是逻辑日志,记录的是这个语句的原始逻辑;
Redo Log是循环写的,空间会用完。Binlog是可以追加写,不会覆盖之前的日志信息。
关于Binlog的sync_binlog配置:
当sync_binlog = 0时,表示MySQL不控制Binlog的刷新,由文件系统自己控制。此时性能最好,风险最大。遇到crash,binlog_cache的数据都会丢失
当sync_binlog > 0时,表示事务每sync_binlog次提交,MySQL就会将操作写入Binlog文件中。
数据更新流程
执行流程:
执行器先从引擎中找到数据,如果在内存中直接返回,如果不在内存中,查询后返回;
执行器拿到数据之后会先修改数据,然后调用引擎接口重新写入数据;
引擎将数据更新到内存,同时写数据到Redo Log中,此时处于prepare阶段,并通知执行器执行完成,随时可以操作;
执行器生成该操作的Binlog;
执行器调用引擎的事务提交接口,引擎把刚写完的Redo Log改为commit状态,更新完成。
具体流程图如下:
蓝色表示在InnoDB内部执行,红色表示在执行器中执行。需要注意Redo Log的写入被拆分为prepare和commit两个阶段。
Redo Log的两段提交
因为涉及到数据的安全问题,所以要分两个阶段提交。假设现在需要将testColumn从0更新为1,出现了这样两种Crash的情况:
**先写Redo Log再写Binlog:**假设Redo Log写完并且Binlog还没写完的时候,MySQL进程异常重启。Redo Log将testColumn由0恢复为1,但Binlog中没有该记录。之后使用Binlog备份日志的时候,testColumn的值还是0,与原来的值不同;
**先写Binlog后写Redo Log:**假设在Binlog写完后,Redo Log还没写,MySQL进程异常重启。由于Redo Log中没有相应日志,此时库中testColumn值为0,但是使用Binlog恢复的日志中testColumn为1,两者不同。