0%

日志与索引

两阶段提交的不同时刻,MySQL异常重启。

崩溃恢复crash safe:

  1. 如果redo log里面的事务是完整的,也就是已经有了commit标识,则直接提交。
  2. 如果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的结果来发现。

redo log和binlog是怎么关联起来的

他们有一个共同的数据字段—XID。崩溃恢复的时候,会按顺序扫描redo log;

  • 如果碰到既有prepare、又有commit的redo log,就直接提交;
  • 如果碰到只有prepare、而没有commit的redo log,就拿着XID去binlog找对应的事务。

处于prepare阶段的redo log加上完整binlog,重启就能恢复,MySQL为什么要这么设计

binlog写完以后MySQL发生崩溃,这时候binlog已经写入了,之后就会被从库(或者用这个binlog恢复出来的库)使用。

所以在主库上也要提交这个事务。采用这个策略,主库和备库的数据就保证了一致性。

只用binlog就能恢复,为什么需要两阶段提交

两阶段提交是经典的分布式系统问题。

对于InnoDB引擎来说,如果redo log提交完成了,事务就不能回滚(如果还允许回滚,就可能覆盖掉别的事务的更新)。如果redo log直接提交,然后binlog写入的时候失败,InnoDB又回滚不了,数据和binlog日志不一致。

两阶段提交是为了给所有人一个机会,当每个人都OK的时候,再一起提交。

不引入两个日志,也就没有两阶段提交的必要了。只用binlog来支持崩溃恢复,又能支持归档

历史原因
InnoDB并不是MySQL的原生存储引擎。MySQL的原生引擎是MyISAM,设计之初就没有支持崩溃恢复。

InnoDB在作为MySQL的插件加入MySQL的引擎家族之前,就已经是一个提供了崩溃恢复和事务支持的引擎了。

InnoDB接入了MySQL后,发现binlog没有崩溃恢复的能力,就用InnoDB原有的redo log。(redo log是在innodb引擎实现的,binlog是在server层实现的)

实现原因
binlog没有能力恢复”数据页”。
InnoDB引擎使用的是WAL技术,执行事务的时候,写完内存和日志,事务就算完成了。如果之后崩溃,要依赖于日志来恢复数据页。
未提交的事务,重启后引擎内部事务会回滚,然后应用binlog可以补回来;已经提交了的事务是可能丢失的(MySQL写数据写在内存里已提交但未落盘,恢复只关注未提交但已写binlog的数据),而且是数据页级的丢失。
binlog里面并没有记录数据页的更新细节,只记录了逻辑操作,数据页是补不回来的。

一个事务的binlog如果回放,就是重做整个事务,一个事务更新的可能不止一个page。

只用redo log,不要binlog

如果只从崩溃恢复的角度来讲可以。可以把binlog关掉,这样就没有两阶段提交了,但系统依然是crash-safe的。

正式的生产库上,binlog有着redo log无法替代的功能。

  • 归档,redo log是循环写,写到末尾是要回到开头继续写的。这样历史日志没法保留,redo log也就起不到归档的作用。
  • MySQL系统以来binlog。binlog作为MySQL一开始就有的功能,被用在了很多地方。其中MySQL系统高可用的基础就是binlog复制。
  • 异构系统(数据分析类系统),这些系统依靠消费MySQL的binlog来更新自己的数据。关掉binlog的话,下游系统没有输入。

redo log一般设置多大

redo log太小的话,会导致很快就被写满,然后不得不强行刷redo log,这样WAL机制的能力发挥不出来。

对于几个TB的磁盘,redo log设置为4个文件,每个文件1GB。

正常运行中的实例,数据写入后的最终落盘,是从redo log更新过来的还是从buffer pool更新过来的(rodo log里面到底是什么)

redo log并没有记录数据页的完整数据,它并没有能力去更新磁盘数据页,页不存在“数据最终落盘,是由redo log更新过去”的情况。

  • 如果是正常运行的实例,数据页被修改以后,跟磁盘的数据页不一致,称为脏页。最终数据落盘,就是把内存中的数据页写盘。这个过程跟redo log毫无关系。
  • 在崩溃恢复场景中,InnoDB如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将它督导内存,然后让redo log更新内存内容,更新完成后,内存页变成脏页,就回到了第一种情况的状态。

redo log buffer是什么,是先修改内存,还是先写redo log文件

在一个事务的更新过程中,日志是要写多次的。每次生成的日志都得先保存起来,但又不能在还没commit的时候就直接写到redo log文件里。

redo log buffer就是一块内存,用来先存redo 日志的。真正把日志写到redo log文件(文件名是id_logfile+ 数字),是在执行commit语句的时候做的。

prepare的时候就要落盘一次(包含一次写数据到盘)。一个大事务中,如果生成的redo log很多,在事务未提交之前,log也会从redo log buffer写到redo log file里。

事务执行过程中不会“主动去刷盘”,以减少不必要的IO消耗。但是可能会出现“被动写入磁盘”,比如内存不够、其他事务提交等情况。

单独执行一个更新语句的时候,InnoDB会自己启动一个事务,在语句执行完成的时候提交。过程跟上面一样,只不多是“压缩”到了一个语句里面完成。

binlo 有对应的binlog cache,binlog写完磁盘就发送给从库。