巅峰战队|详解一条查询select语句和更新update语句的执行流程( 五 )


可能有人会问 , 为什么会有两份日志呢?因为最开始 MySQL 里并没有 InnoDB 引擎 。 MySQL 自带的引擎是 MyISAM , 但是 MyISAM是不支持事物的 , 也没有崩溃恢复(crash-safe)的能力 , binlog日志只能用于归档 。 那么既然InnoDB是需要支持事务的 , 那么就必须要有崩溃恢复(crash-safe)能力 , 所以就使用另外一套自己的日志系统 , 也就是基于redo log 来实现 crash-safe 能力 。
bin log和redo log的区别1、redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的Server层实现的 , 所有引擎都可以使用 。 2、redo log 是物理日志 , 记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志 , 记录的是这个语句的原始逻辑 , 比如“给id=2 这一行的c字段加 1 ” 。 3、redo log 是循环写的 , 空间固定会用完;binlog 是可以追加写入的 。 “追加写”是指 binlog 文件写到一定大小后会切换到下一个 , 并不会覆盖以前的日志 。
update语句的执行流程前面铺垫了这么多 , 主要是想让大家先理解redo log和big log这两个概念 , 因为更新操作离不开这两个文件 , 接下来我们正式回到正题 , 一条update语句到底是如何执行的 , 可以通过下图表示:
巅峰战队|详解一条查询select语句和更新update语句的执行流程上图可以大概概括为以下几步:1、先根据更新语句的条件 , 查询出对应的记录 , 如果有缓存 , 也会用到缓存2、Server端调用InnoDB引擎API接口 , InnoDB引擎将这条数据写到内存 , 同时写入redo log , 并将redo log状态设置为prepare3、通知Server层 , 可以正式提交数据了4、Server层收到通知后立刻写入bin log , 然后调用InnoD对应接口发出commit请求5、InnoDB收到commit请求后将数据设置为commit状态
上面的步骤中 , 我们注意到 , redo log会经过两次提交 , 这就是两阶段提交 。
两阶段提交两阶段提交是分布式事务的设计思想 , 就是首先会有请求方发出请求到各个服务器 , 然后等其他各个服务器都准备好之后再通知请求方可以提交了 , 请求方收到请求后再发出指令 , 通知所有服务器一起提交 。
而我们这里redo log是属于存储引擎层的日志 , bin log是属于Server层日志 , 属于两个独立的日志文件 , 采用两阶段提交就是为了使两个日志文件逻辑上保持一致
假如不采用两阶段提交法假如有一条语句id=1,age=18 , 我们现在要把这条数据的age更新为19:

  • 先写 redo log 后写 binlog假设在redo log 写完 , binlog还没有写完的时候 , MySQL发生了宕机(crash) 。 重启后因为redo log写完了 , 所以会自动进行数据恢复 , 也就是age=19 。 但是由于binlog没写完就宕机( crash)了 , 这时候 binlog 里面就没有记录这个语句 。 因此 , 之后备份日志的时候 , 存起来的 binlog 里面就没有这条语句 。 然后某一天假如我们把数据丢失了 , 需要用bin log进行数据恢复就会发现少了这一次更新 。
  • 先写binlog后写redo log假如在binlog写完 , redo log还没有写完的时候 , MySQL发生了宕机(crash) 。 重启后因为redo log没写完 , 所以无法进行自动恢复 , 那么数据就还是age=18了 , 然后某一天假如我们把数据丢失了 , 需要用binlog进行恢复又会发现恢复出来的数据age=19了 。
通过以上的两个假设我们就会发现 , 假如不采用两阶段提交法就会出现数据不一致的情况 , 尤其是在有主从库的时候 , 因为主从复制是基于binlog实现的 , 如果redo log和bin log不一致 , 就会导致主从库数据不一致 。
宕机后的数据恢复规则1、如果 redo log 里面的事务是完整的 , 也就是已经有了 commit 标识 , 则直接提交;2、如果 redo log 里面的事物只有完整的 prepare , 则判断对应的事务 binlog 是否存在并完整:如果是 , 则提交事务;否则 , 回滚事务 。