15 | 答疑文章(一):日志和索引相关问题

两阶段提交示意图:

这个图不是一个update语句的执行流程吗,怎么还会有commit语句?

混淆了两个"commit"的概念

  • 他说的"commit语句",是指MySQL语法中,用于提交事务的命令。一般跟begin/start transaction配对使用。
  • 而我们图中用到的这个"commit步骤",指的是事务提交过程中的一个小步骤,也是最后一步,当这个步骤执行完成后,这个事务就提交完成了。
  • “commit语句”执行的时候,会包含“commit步骤”。

 

两阶段提交的不同时刻,MySQL异常重启会出现什么现象

如果在图中A时刻的地方,也就是写入redo log处于prepare阶段之后,写binlog之前,发生了奔溃(crash),由于此时binlog还没写,redo log也还没提交,所以奔溃恢复的时候,这个事务会回滚。这时候,binlog还没写,所以也不会传到备库。

如果是时刻B呢?

奔溃恢复时的判断规则:

  1. 如果redo log里面的事务是完整的,也就是已经有了commit标识,则直接提交;
  2. 如果redo log里面的事务只有完整的prepare,则判断对应的事务binlog是否存在并完整:a.如果是,则提交事务;b.否则,回滚事务。

时刻B发生crash对应的就是2(a)的情况,奔溃恢复过程中事务会被提交。

 

MySQL是怎么知道binlog是完整的?

一个事务的binlog是有完整格式的:

  • statement格式的binlog,最后会有commit;
  • row格式的binlog,最后会有一个XID event。

另外,MySQL 5.6.2版本后,还引入了binlog-checksum参数,用来验证binlog内容的正确性。对于binlog日志由于磁盘原因,可能会在日志中间出错的情况,MySQL可以通过校验checksum的结果来发现。所以MySQL还是有办法验证事务binlog的完整性的。

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

它们有一个共同的字段,叫XID。奔溃恢复的时候,会按顺序扫描redo log:

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

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

其实,这个问题还是跟我们在反证法中说到的数据与备份一致性有关。在时刻B,也就是binlog写完后MySQL发生奔溃,这时候binlog已经写入了,之后就会被从库(或者用这个binlog恢复出来的库)使用。

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

如果这样的话,为什么还要两阶段提交呢?干脆redo log写完,再写biblog。奔溃恢复的时候,必须两个日志都完整才可以,是不是一样的逻辑?

其实两阶段提交时经典的分布式问题,并不是MySQL独有的。

如果必须要举一个场景,来说明这么做的必要性的话,那就是事务的持久性问题。

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

两阶段提交就是为了给所有人一个机会,当每个人都说“我ok”的时候,再一起提交。

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

这样的话提交流程了改成这样了:... → “数据更新到内存” → “写binlog” → “提交事务”,是不是也可以提供奔溃恢复的能力?

答案是不可以。

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

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

InnoDB接入了MySQL后,发现既然binlog没有奔溃恢复的能力,那就用InnoDB原有的redo log好了。

如果说实现上的原因的话,就比较多了,按照问题中说的,只用binlog来实现奔溃恢复的流程,就成了如下图:

这样的流程下,binlog还是不能支持奔溃恢复的,其中一个不支持的点:binlog没有能力恢复“数据页”。

那能不能反过来,值用redo log,不要binlog?

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

但是,业界在正式的生产库上,binlog都是开着的,因为binlog有这redolog无法替代的功能。

一个是归档。redo log是循环写,写到末尾是要回到开头来继续写的。这样历史的日志没法保留,redo log也就起不到归档的作用。

一个就是MySQL系统以来与binlog。binlog作为MySQL一开始就有功能,被用在了很多地方,其中,MySQL系统高可用的基础,就是binlog复制。

还有很多公司又异构系统(比如一些数据分析系统),这些系统就靠消费MySQL的binlog来更新自己的数据。关掉binlog的话,这些下游系统留没法输入了。

总之,由于现在包括MySQL高可用在内的很多系统机制都依赖于binlog。

redo log一般设置多大?

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

所以,如果是现在常见的几个TB的磁盘的话,就不要太小气了,直接将redo log设置为4个文件,每个文件1GB吧。

正常运行中的实例,数据写入后的最终落盘,是从redo log更新过来的还是从buffer pool更新过来的呢?

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

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

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

在一个事务的更新过程中,日志是要写多次的,比如下面这个事务:

begin;

insert into t1...

insert into t2...

commit;

这个事务要往两个表中插入记录,插入数据的过程中,生成的日志都得先保存起来,但又不能在还没commit的时候就直接写到redo log文件里。

所以,redo log buffer就是一块内存,用来先保存日志的。也就是说,在执行第一个insert 的时候,数据的内存被修改了,redo log buffer也写入了日志。

但是,真正把日志写到redo log文件(文件名是ib_logfile+数字),是在执行commit语句的时候做的。

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

上一篇:14 | count(*)这么慢,我该怎么办?

下一篇:16 | “order by”是怎么工作的?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值