mysql在事务执行时,需要写入两种日志,一种是server层的binlog,另一种是引擎层的redo log。事务commit时,以上两种类型的日志的写入需要遵循两段式提交协议。
什么是两段式提交?
为了保证分布式事务场景下事务的一致性。因为在分布式背景下,事务语句来自不同实例,因此需要一个协调者角色。每一个实例的语句先执行prepare,并且将结果告知协调者,如果全部成功,协调者再发出commit命令,各个实例再执行提交。防止部分实例失败,导致分布式事务语句没有全部执行。当然两阶段提交并不能完全解决问题,只是一种思路。
mysql里,因为binlog和redo log也是不同层面的两种日志,也有分布式事务的特征,即要么全部成功要么全部失败,所以也使用了两段式提交的思路。
为什么必须两段式提交?
因为如果redo log和binlog只有一个执行成功,将会导致主从不一致。比如只有redo log成功,那么从库无法更新;如果只有binlog成功,主库无法更新。
redo log和binlog为什么必须是两个?能否合二为一?
个人认为,理论上设计一种新的log,可以具备redo log和binlog的全部特性,那当然是可以的。但是,mysql在发展过程中的演化,导致了现在两种log并存的场面。redo log是innodb引擎特有的日志,在引擎内生成,用于保证事务的持久性。binlog是mysql的server层日志,用于做归档,可以复制和增量备份。binlog是server层特性,与用什么引擎没有关系。
那么,在目前的情况下,二者是否可以合二为一?
比如只用redo log,不行,因为只有innodb采用redo log,这样换成其他引擎不就玩完了?什么,让其他引擎也强加上redo log?那也不行,因为redo log不支持归档,redo log是循环写入的,写完后就必须清空之前的文件才能继续写入;
那只用binlog,也不行,因为binlog并不会记录更新的细节信息,比如修改了哪个页的哪一个偏移位置的数据,只有binlog缺乏恢复数据的能力。
redo log和binlog如何关联?
有一个公共的字段叫做XID,在两个日志里都存在。可以当成是事务的唯一id。
两段式提交过程?
先执行redo log的prepare阶段。会将事务内容写入redo log。事务处于prepare阶段。具体怎么写由innodb_flush_log_at_trx_commit参数控制:
0:只写入log buffer,不持久化;
1:调用fsync持久化至磁盘;
2:调用write写入文件系统的page cache,由操作系统决定持久化时机;
另外,mysql会有一个后台线程每隔一秒将log buffer的redo log持久化至磁盘,fsync方式。
一般会选择设置为2,这样性能好一些,但是如果在fsync之前,系统挂了,redo log就丢了。而对于金融类的系统,需要较高可靠性,可以设置为1。
接着会执行binlog的写入,在事务提交之前,binlog也是记录到binlog cache中的,在提交时,会将binlog持久化,具体行为由另一个参数sync_binlog控制:
0:调用write写入文件系统的page cache;
1:调用fsync持久化至磁盘;
N:每次提交只write,直到累积N个事务后再fsync;
同样的,对于金融类等系统,需要设置为1。两个参数都设置为1的配置方式被称为“双1配置”,可以保证不丢数据。
最后执行两段式提交的commit,将本地redo log commit;
如何恢复数据?
如果写binlog前挂了,事务不会在主库和从库提交,需要回滚这部分事务;
如果commit前挂了,事务处于prepare,但是已经写了binlog,由于binlog有可能没有fsync,所以需要判断binlog是否完整。怎么取?从本地redo log中取到对应事务的XID,看XID对应的事务是否已经在binlog中被提交,如果是,则提交本地redo log,否则回滚;那又是如何判断binlog是否完整?如果是语句模式,会有commit语句;如果是行模式,会有XID Event;
当然由于prepare阶段redo log的并一定fsync,所以这里可能会丢本地数据。
组提交
实际上,为了优化fsync性能,binlog和redo log在持久化时都使用了组提交机制,也就是一组日志一起fsync,提升了性能。
如何保证两个log的执行顺序
待补充