15 | 日志相关问题
日志相关问题
在两阶段提交的不同瞬间,MySQL如果发生异常重启,是怎么保证数据完整性的?
下在两阶段提交的不同时刻,MySQL异常重启会出现什么现象。
在图中时刻A的地方,也就是写入redo log 处于prepare阶段之后、写binlog之前,发生了崩溃(crash),由于此时binlog还没写,redo log也还没提交,所以崩溃恢复的时候,这个事务会回滚。这时候,binlog还没写,所以也不会传到备库。
在时刻B,也就是binlog写完,redo log还没commit前发生crash,那崩溃恢复的时候MySQL会怎么处理? 我们先来看一下崩溃恢复时的判断规则。
- 如果redo log里面的事务是完整的,也就是已经有了commit标识,则直接提交;
- 如果redo log里面的事务只有完整的prepare,则判断对应的事务binlog是否存在并完整:- a. 如果是,则提交事务;- b. 否则,回滚事务。
MySQL怎么知道binlog是完整的?
答:一个事务的binlog是有完整格式的:
- statement格式的binlog,最后会有COMMIT;
- row格式的binlog,最后会有一个XID event。 在MySQL5.6.2版本以后,还引入了binlog-checksum参数,用来验证binlog内容的正确性。对于binlog日志由于磁盘原因,可能会在日志中间出错的情况,MySQL可以通过校验checksum的结果来发现。所以,MySQL还是有办法验证事务binlog的完整性的。
redo log 和bin log是怎么关联起来的?
答:它们有一个共同的数据字段,叫XID。崩溃恢复的时候,会按顺序扫描redo log:
- 如果碰到既有prepare、又有commit的redo log,就直接提交;
- 如果碰到只有parepare、而没有commit的redo log,就拿着XID去binlog找对应的事务。
处于prepare阶段的redo log加上完整binlog,重启就能恢复,MySQL为什么要这么设计?
答:在时刻B,也就是binlog写完以后MySQL发生崩溃,这时候binlog已经写入了,之后就会被从库(或者用这个binlog恢复出来的库)使用。所以,在主库上也要提交这个事务。采用这个策略主库和备库的数据就保证了一致性。
如果这样的话,为什么还要两阶段提交呢?干脆先redo log写完,再写binlog。崩溃恢复的时候,必须得两个日志都完整才可以。是不是一样的逻辑?
答:其实,两阶段提交是经典的分布式系统问题,并不是MySQL独有的。
这么做的必要性就是事务的持久性问题。
对于InnoDB引擎来说,如果redo log提交完成了,事务就不能回滚(如果这还允许回滚,就可能覆盖掉别的事务的更新)。而如果redo log直接提交,然后binlog写入的时候失败,InnoDB又回滚不了,数据和binlog日志又不一致了。 两阶段提交就是为了给所有人一个机会,当每个人都说“我ok”的时候,再一起提交。
只用binlog来支持崩溃恢复,又能支持归档,不就可以了?
答:不可以。binlog还是不能支持崩溃恢复的:binlog没有能力恢复“数据页”。
反过来,只用redo log,不要binlog?
-回答:如果只从崩溃恢复的角度来讲是可以的。你可以把binlog关掉,这样就没有两阶段提交了,但系统依然是crash-safe的。因为binlog有着redo log无法替代的功能。
- 一个是归档,保留历史日志。
- 一个就是MySQL系统依赖于binlog,MySQL系统高可用的基础,就是binlog复制。
redo log buffer是什么?是先修改内存,还是先写redo log文件?
在一个事务的更新过程中,日志是要写多次的。比如下面这个事务:
begin;
insert into t1 ...
insert into t2 ...
commit;
这个事务要往两个表中插入记录,插入数据的过程中,生成的日志都得先保存起来,但又不能在还没commit的时候就直接写到redo log文件里。
所以,redo log buffer就是一块内存,用来先存redo日志的。也就是说,在执行第一个insert的时候,数据的内存被修改了,redo log buffer也写入了日志。 但是,真正把日志写到redo log文件(文件名是 ib_logfile+数字),是在执行commit语句的时候做的。
单独执行一个更新语句的时候,InnoDB会自己启动一个事务,在语句执行完成的时候提交。过程跟上面是一样的,只不过是“压缩”到了一个语句里面完成。