重读MySQL45讲002——一条SQL更新语句是如何执行的

126 阅读6分钟

写在前面

第2讲在接着第1讲,在查询语句的基础上讨论更新的语句,是想在复习前一节的基础上,也引出MySQL中和更新相关的两个重要Log模块——redolog和binlog。

查询与更新

第2讲主要讨论一个SQL更新语句。其实更新=查询+更新。先找到要更新的数据,然后按照SQL条件做对应的更新。

因此也复习了第1讲的:连接器->分析器->优化器->执行层->存储引擎,的基本执行路径。

WAL

这里通过讨论更新SQL,引出了MySQL对于高并发更新场景的解决方案:write-ahead logging。也就是先写日志,然后写磁盘。换个角度理解,WAL就是在客户端->磁盘之间增加了一个中间角色的技术,让redolog承担这个中间角色,让他一方面应对高并发的客户端写入请求,另一方面异步兼容磁盘较慢的写入能力,适时的完成数据刷盘。

基本实现原理就是:

  1. 当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log(粉板)里面,并更新内存,至此完成客户端的更新操作。
  2. 而后InnoDB引擎会在系统空闲时,将这个操作记录更新到磁盘里面。

当客户端写入速度小于引擎写入磁盘速度时,引擎更新到磁盘会游刃有余。而反之,则按上述描述的模型推演,就会出现引擎暂停,优先完成log到磁盘的更新,而后在处理客户端的写入请求。

所以这个模型还是有一些缺陷的,也有相应的改进措施,但第2讲还没说,所以先按下不表。

redolog与binlog

而后第2讲接受了MySQL中承担WAL实现的具体组件:redolog与binlog。并做了如下的比较:

  1. redolog是innodb独有,而binlog是mysql Server层共有的
  2. redolog记录实际物理磁盘数据页上的操作,而binlog记录了原始的sql语句
  3. redolog是循环写入,在将新纪录写入redolog的同时,也需要innodb引擎不断将redolog同步到磁盘中并清除redolog的日志;而binlog是保持顺序写入的。

更新的操作流程

在了解了WAL和redolog之后,再回头看一下更新流程(查询+更新),会发现有如下不同:

  1. 因为有了WAL,所以在执行一条语句时,其目标操作数据有可能在磁盘中,也有可能在内存中,所以在查询时,会Innodb引擎会先看内存中是否有目标操作数据,如果有,则不用访问磁盘,可直接返回。如果没有,则访问磁盘,并将数据读入内存,而后返回;
  2. 找到目标操作数据之后,在Server层完成更新操作,并将更新后的数据返回给Innodb引擎。
  3. Innodb引擎接受到新的数据,便会根据WAL技术,先写入内存,写入redolog,更新这次的变更事务状态为prepare,而后写入binlog,更新变更事务状态为commit,完成两阶段提交,完成这次的更新

第2讲在读的时候,会感到困惑,一条更新语句,怎么就跟事务扯上关系了。其实在后面也会提到,这里的两阶段提交是为了保障一条更新语句的「完整」。这里的「完整」是指在这样的两阶段提交过程中,任何一个环节程序意外崩溃,Innodb恢复之后都不会产生数据不一致的问题。

换个角度看看

我在学习这段时也会感到困惑。我觉得困惑的原因是因为作者在引入redolog和binlog时,缺少一些铺垫就又引入更新时的两阶段提交。

redolog和binlog能干嘛

redolog一方面可以作为WAL技术的基础,提升引擎写入性能。同时和他的名字一样,他也记录了一段时间内MySQL更新的数据,可以用于系统崩溃时的数据恢复。

binlog也能用于数据恢复,但是因为他记录了一台MySQL实例完整的SQL语句,所以他一般用于多台MySQL实例之间的主从同步(想要完全成为另一个人的办法就是完全经历他所有的过去)。

那么当一条更新语句到来时,需要更新redolog也需要更新binlog,就需要处理好二者更新的顺序,不然在MySQL崩溃恢复时,自身实例依赖redolog,而从实例依赖binlog,恢复之后二者数据不一致,就会很尴尬

分布式两阶段提交

两阶段提交是一个成熟的分布式事务一致性的解决方案。和这里的两阶段提交有相似性,但不完全相同。分布式的两阶段提交是在处理多个分布式实例同时做变更时,如何保证要么全部成功,要么全部失败。

它会通过引入一个协调者的角色,让执行变更的多个分布式实例(参与者)先各自做变更,完成变更的向些调整提交提交prepare状态,当协调者接受到所有变更都是完成,便可以让参与者完成commit,否则rollback。

redolog与binlog的两阶段提交

可以说只借鉴了prepare与commit的两个操作概念。

而后这一讲论述了是否需要两阶段提交的问题,他采用的方法是反证法:如果不是用两阶段提交,顺序执行redolog和binlog的写入,这又会有两种情况:

  1. 先写redolog后写binlog:考虑在两次写入的中间时刻主实例崩溃。主实例有redolog可以恢复数据,但缺失binlog,从实例按照binlog恢复就缺失了这部分数据,这样就主从不一致。
  2. 先写binlog后写redolog:也考虑在两次写入的中间时刻主实例崩溃。主实例没有redolog,事务无效,主实例恢复之后,便缺失这部分数据。而redolog有,从实例就有这部分数据,这样也主从不一致了。

所以需要两阶段提交。那考虑如果有两阶段提交,为啥不先写binlog再写redolog呢?这个要考虑另一个场景:binlog在线上是实时生产并被从实例实时消费同步的。如果先写binlog,那么主实例崩溃,对于主实例这条binlog无效(因为没有完成两阶段提交),但对于从实例,他可能已经完成了binlog的消费,这就导致主从不一致了。

但两阶段先写redolog,再写binlog,就不会有这个问题。

当然,严格的证明还有:为啥不只写redolog?为啥不只写binlog?这个后面有专门的答疑解决。所以这里聚焦于主要矛盾。

主要矛盾就是:

  1. 需要知道Innodb使用了WAL来提升更新写入性能;
  2. Innodb使用了redolog。redolog是循环写入的,需要系统空闲清理redolog,刷盘。
  3. Innodb的更新在更新redolog和binlog,是使用了两阶段提交,先写redolog,然后prepare,然后写binlog,然后commit的。

写在后面

  1. juejin.cn/post/698983…
  2. zhuanlan.zhihu.com/p/35616810