redo log和binlog的一些好问题
开始之前,放上之前画的一张关于update执行流程的图:
下面,我们结合这张图来看这些关于binlog和redo log的好问题。
MySQL的两阶段提交能够保证binlog和redo log的一致性。
那么如果在两阶段提交的过程中,发生了数据库的崩溃,MySQL内部会做什么事情来保证数据的一致性呢?以上述的update操作为例:
a、当MySQL在新行记录写入redo log之前发生了崩溃,因为redo log还没有写入,内存中的更新会丢失,此时事务没有提交,所以MySQL再次重新启动之后,会将这个事务进行回滚。
b、当MySQL在新行记录写入了redo log之后,也就是时刻1发生了crash,那么redo log目前是prepare阶段,而binlog没有写入,此时MySQL同样会进行回滚。
c、当MySQL在新行记录写入了redo log之后,binlog也写入了,此时在时刻2发生crash,这个时候,因为MySQL在恢复的时候,会做如下两个判断动作:
1、如果redo log里面的事务是完整的,也就是已经有了commit标识,则直接提交;
2、如果redo log里面的事务只有完整的prepare,则判断对应的事务binlog是否存在并完整: a. 如果是,则提交事务; b. 否则,回滚事务。
如果binlog是statement模式的,最后面会出现一个commit的标识,如下:
use `test`/*!*/;
SET TIMESTAMP=1590418900/*!*/;
insert into test values (1,'zhangsan')
/*!*/;
# at 490
#200525 23:01:40 server id 2025725 end_log_pos 521
COMMIT/*!*/;
如果binlog是row模式的,最后面会出现一个xid的event事件,如下:
### UPDATE `mysql`.`ha_health_check`
### WHERE
### @1=1582266682672 /* LONGINT meta=0 nullable=1 is_null=0 */
### @2='m' /* STRING(3) meta=65027 nullable=0 is_null=0 */
### SET
### @1=1582266698557 /* LONGINT meta=0 nullable=1 is_null=0 */
### @2='m' /* STRING(3) meta=65027 nullable=0 is_null=0 */
# at 790
#200221 14:31:38 server id 3141372998 end_log_pos 821 CRC32 0xf63b387d Xid = 1289289427
COMMIT/*!*/;
# at 821
#200221 14:31:44 server id 3141372998 end_log_pos 886 CRC32 0xc01e5aea GTID last_committed=2 sequence_number=3
除此之外,MySQL5.6中还引入了binlog checksum的参数,用来确认binlog的正确性,一般情况下,这个参数在主从上的设置应该保持一致,要么都为none,要么都为CRC32
redo log对于用户是不可见的,如果你强制用vim打开redo log,你会看到一堆乱码。在binlog中,我们可以看到binlog的xid值,这个值就是用来关联redo log和binlog的。
其实对于主库来讲,redo log和binlog要么同时存在,要么同时回滚,都不影响redo log和binlog的一致性。之所以在redo log prepare阶段完成、binlog写入后让事务提交,本质上还是为了保证主库和从库的一致性。因为binlog一旦写入,会通过dump thread同步给从库,从库会应用这个binlog,那么如果主库上crash之后,将写入的binlog回滚了,就有可能造成主库和从库的数据不一致现象。
如果只有binlog,那么MySQL的执行逻辑将变成:
数据更新到内存---写binlog---提交事务.
这种情况下,如果写完binlog之后MySQL发生了crash,那么内存中的数据页是无法修复的,由于MySQL采用的是WAL技术,也就是先写内存日志再写磁盘,而binlog是没有能力恢复损坏的内存数据页的。
如果只有redo log,那么因为redo log是循环写的,也就没有办法保留很长的周期,失去了binlog归档变更操作的功能。再者主从复制的结构可能会更脆弱,高可用架构也就更谈不上了。
其实数据落盘和redo log是没有关系的,redo log本身不记录数据页的完整数据,它只记录数据也的物理变更。
数据的落盘其实是将buffer pool中的脏页刷新到磁盘的过程。所谓的脏页,就是buffer pool中被修改的和磁盘上不一致的数据页。
在MySQL崩溃回复的过程中,如果发现某个数据页可能在崩溃回复的过程中,丢失了更新,就会将这个数据页加载到内存,也就是buffer pool,让redo log更新内存中的内容。更新完成之后,这个数据页就变为脏页,可以刷新回磁盘了。
假设有这么一个事务:
begin;
insert xxx 1
insert xxx 2
commit;
在commit之前,需要保存这两个insert产生的redo log,但是又不能直接写入到redo log文件里面,此时这些redo log就先保存在redo log buffer里面,当我们执行commit的时候,才会把redo log写入到iblogfile里面。
所以写入顺序上来讲,redo log buffer先写入,而redo log文件后写入。