接上文MySQL InnoDB中一个update语句从执行到提交的全过程(2)-CSDN博客
目录
六、本地提交
怎样保证binlog和redo log的状态一致呢?
MySQL 中的内部 XA 机制
宕机时不同状态的处理
物理落盘策略
七、主备复制
八、返回提交成功
总结一下
九、脏页刷入磁盘
编辑
六、本地提交
修改完成之后,还有一个至关重要的步骤,那就是commit。只有完成commit,这个update才能真正地持久化下来,不会因为MySQL的任何故障而丢失。
对于Innodb存储引擎而言,提交过程真正需要写入的只有redolog。
Innodb存储引擎中,redolog落盘的步骤是什么?
redo日志文件的结构
- MySQL 8.0.30之前,redo日志文件默认有两个:ib_logfile0和ib_logfile1,两个日志
- 文件会进行交替的写入;
- MySQL 8.0.30,对redo log进行了重构,允许了redo log空间的动态修改。
提交阶段InnoDB存储引擎要落盘redo log,MySQL服务器层要落盘binlog(binlog在之前的事务执行阶段就会生成)。
怎样保证binlog和redo log的状态一致呢?
MySQL 中的内部 XA 机制
MySQL采用了一种内部XA事务的机制保证binlog和redolog的状态和顺序都一致,其核心是我们在分布式领域耳熟能详的两阶段提交(2PC),具体过程如下:
-
prepare阶段:InnoDB刷redo log到磁盘,redo log落盘完成后,修改事务状态为TRX_PREPARED。
-
prepare如果失败,那么事务会回滚;而prepare成功后,就进入两阶段提交的commit阶段。
-
-
commit阶段:MySQL 服务器层会首先将 Binlog 写入磁盘,写入完成后,修改事务状态为TRX_NOT_STARTE。事务的提交就算成功了。
宕机时不同状态的处理
- 事务状态为TRX_ACTIVE,那么直接回滚事务;
- 事务状态为TRX_NOT_STARTED,表示事务的redo log和binlog都已落盘,认为事务已经提交;
- 恢复时如果发现一个事务状态为TRX_PREPARED,根据binlog的写入状态来判断提交还是回滚:
- 如果binlog没写入成功,则回滚
- 如果binlog写入成功,则提交并修改事务状态为TRX_NOT_STARTED
通过这种方式,MySQL 能够确保在系统崩溃的情况下,Binlog 和 Redo Log 保持一致性,即不会出现 Binlog 已记录但 Redo Log 未提交的情况,或反之亦然。这种一致性保证对于基于 Binlog 的主从复制和 Point-in-Time 恢复非常关键。
物理落盘策略
只不过上面提到的都是逻辑上,实际是否把日志写入磁盘,与物理落盘策略有关,由下面俩参数控制:
- nnodb_flush_log_at_trx_commit:控制redo log的落盘,可选值为0,1,2。
- 0表示每秒进行一次刷新
- 1表示每次事务提交都会落盘
- 2表示每次事务提交会把redo log缓冲写入操作系统缓冲,每秒刷盘
- sync_binlog:控制binlog的落盘,可选值为0,1。
- 0表示每次提交事务,不刷binlog
- 1表示每次提交事务,都会立刻写binlog到磁盘
- MySQL 8.0默认都设置为1。
这样,我们的SQL就在当前MySQL服务器上提交完成了。
七、主备复制
但在很多场景下,这并不意味着我们直接可以返回客户端提交成功了,如果配置了主从复制,还需要根据我们的同步策略来判定是否符合提交成功的条件。
主从复制的策略
- 异步复制:主库写完binlog后即可返回提交成功,无需等待备库响应
- 半同步复制:主库接收到指定数量的备机转储relay log成功的ACK后可返回提交成功;
- 同步复制:主库等到备库回放relaylog执行完事务之后才可返回提交成功。
因为半同步复制兼具了高可用性和性能,所以我们通常都会选择半同步复制的策略。
假设我们这条SQL是发往一个一主两备的MySQL集群,配置的备机响应数=1,那么主库接收到一个备库转储relaylog成功的响应后,即可返回提交成功。
此时,如果备机一直没有响应怎么办?难不成一直等下去?Mysql原生的半同步复制策略有一个超时时间,超过这个时间还没有备机响应的话,主机就自动提交了。显然这是很不安全的,因为这种情况下半同步复制退化成了异步复制策略。所以很多开源工具和基于MySQL的分布式数据库都在这里进行了一些改造。
八、返回提交成功
到这里,客户端收到了提交成功的反馈,可以认为整个事务已经结束了,修改命令已经执行完成,并且持久化在了我们的MySQL数据库中。
总结一下
(1)首先,任何sql都要在事务中执行,所以我们的第一步就是开启事务,但不会在这里就分配事务号。接下来update sql就发往mysql服务器层,这时我们分配的T024这个事务号。
(2)然后经过了sql解析、词法分析、语法分析。查询优化等步骤,最终生成一个物理执行计划,通过这个物理执行计划,mysql执行器开始调用Innodb存储引擎的接口进行后面的修改操作。
(3)修改这项数据之前把它查出来,我们先根据根节点的页号获取到B+树的根节点,然后解析根节点这个索引页,通过其中的条件比较找到下一层节点页号,重复这个过程,直至找到叶子节点,解析数据页,从而获取到我们想要的数据。
这个过程中的所有页要么一开始就在buffer pool中,要么从磁盘加载到buffer pool中。
(4)然后我们要对这样数据进行校验锁和加锁。这里会上三个锁,分别是mysql服务器层的元数据锁,Innodb存储引擎层的表级意向IX锁和行级排他X锁。加上锁后,我们就可以对数据进行修改。
(5)这个过程需要在InnoDB存储引擎写入数据页、undolog和redolog,在MySQL服务器层写入binlog。所有的这些在这一步都不用落盘。
(6)修改和生成日志完成后,就可以开始提交了。
主机提交需要redolog和binlog落盘。这里,通过MySQL内部XA事务机制来协调redolog和binlog,保证两个日志的状态一致。
(7)如果配置了主备复制,那么还需要把binlog同步给备机。在最常用的半同复制模式下,有指定数量的备机转储返回成功后即可返回提交成功。如果超过超时时间还没达到指定数量的反馈,则会退化成异步复制,主机直接返回提交成功。
(8)至此,从客户端的角度来说,这个事务提交成功了。
九、脏页刷入磁盘
但在数据库内部的概念里,我们修改后的数据已经还没落盘。
看一下MySQL把脏页落盘的过程。
想象一下MySQL落盘的操作,要把这个16K的页刷到磁盘中的一块区域中,但这里有一个风险。
如果我们写这个页的时候,比如说只写了4K,系统就发生了宕机,这个时候的异常状态怎么处理呢?大家可能第一时间会想到用redolog来恢复,但我们已经解读过redolog的结构。redolog记录页物理结构所做的变更。是一个基于完整且正确,只是老版本的页了。现在我们在磁盘中的页已经被破坏,是不完整的。所以redolog也没法很好的恢复。
这个时候应该怎么处理呢?
MySQL针对这种情况设计了两次写(double write)机制,其实原理很简单,就是先把脏页都复制到doublewrite buffer中,然后doublewrite buffer先落盘,而后脏页再真正落盘。
这样如果发生了宕机恢复时,doublewrite buffer中的页覆盖磁盘中的页,再进一步用redolog恢复,这样就能确保页是完整的,解决了我们的问题。