一条查询语句的执行过程,一般是经过 连接器、分析器、优化器、执行器,最后到达存储引擎。
那么对于一条 更新的 MySQL 语句,执行流程又是怎样的呢? 首先可以肯定的是,以上查询语句经过的流程,更新语句也会走一遍。
但是与查询流程不一样的是,更新流程还涉及到两个重要的日志模块: redo log(重做日志) 和 binlog(归档日志)。
重做日志 redo log
InnoDB 使用到的记录修改日志的技术是 WAL (Write-Ahead logging),其关键点就是先写日志,再写磁盘。
当有一条记录需要更新时, InnDB 引擎会先把记录写入 redo log 中,并更新内存。然后,在适当的时候,将此操作记录更新到磁盘中。
如上所示,为 InnoDB 的 redo log 示意图。 redo log 的大小是固定的,会有两个指针记录关键的位置: write pos 是当前记录位置,checkpoint 是当前要擦除的位置,也就是把记录更新到磁盘。这两个指针都是不断向后推移并循环的。而 write pos 和 checkpoint 之间的位置就是可以用于记录新的操作的空间。
有了 redo log, InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录不会丢失,并且可以根据 redo log 恢复,此能力称为 crash-safe。
归档日志 bin log
上面提到的 redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己特有的日志,称为 binlog。
binlog 和 redo log 有以下几点不同:
1、binlog 是 Server 层实现的, redo log 是 InnoDB 特有的。
2、redo log 是物理日志,记录的是“在某个数据页上做了什么修改”,而 binlog 是逻辑日志,一般记录的是原修改 SQL 语句。
3、redo log 是循环写的,空间有固定大小,并且可能会用完。binlog 是追加写入的,写到一定大小后可以切换下一个,并不会覆盖之前的日志。
以上是 update 语句的流程图,浅色框代表在 InnoDB 内部执行, 深色框是在执行器中执行。
可以注意到,最后三个步骤,将 redo log 的写入拆成了两步骤: prepare 和 commit ,这就是 “两阶段提交”。
两阶段提交
“两阶段提交”的机制,是为了让两份日志之间的逻辑一致。
可以考虑这么一个场景:当发生了误删表的操作,需要如何找回数据:
1、找到最近一次的全量备份
2、从这个备份时间点开始,将备份的 binlog 依次取出,重新放到误删表之前的那个时刻。
3、然后此备份就和误删之前的线上库一样了。
那么,到底为什么需要“两阶段提交”呢? 这里用 反证法举例说明,如果不用两阶段提交,会发生什么?
假设有这么个字段 c=0,现在要将其修改为 c=1
1、 先写 redo log 再写 binlog。如果写完 redo log ,还没来得及写 binlog 时,MySQL 异常重启。此时根据 redo log 恢复的 c=1,但是 binlog 还未写入,根据 binlog 恢复的临时库,c=0,与原库不一致。
2、先写 binlog 再写 redo log。如果在写 redo log 之前,MySQL异常重启,那么此事务无效,c的值应该是0。但是根据 binlog 恢复的临时库,会多一次事务出来,导致与原库值不相同。
网友评论