Mysql为什么要用两个日志模块?为什么采用二段式提交?
首先回答第一个问题,得了解Mysql的日志模块,再开始讲日志模块,先回顾一下Mysql查询一条语句的一个执行过程:
Mysql主要有两个日志模块:redo log
和bin log
,其中redo log
就是重做日志,
redo log(InnoDB特有的日志,发生在引擎层)
redo log是物理日志,记录了这个数据页 “做了什么改动”,发生在InnoDB引擎中,当修改数据时,InnoDB将数据写入内存,在写入redo log就算修改完了,而且redo log是顺序写磁盘速度很快。避免了修改数据时直接修改数据行带来的大量随机IO,采用了随机IO转顺序IO的策略。
redo log顺序写实际上是循环写固定几个文件,写满一轮就要从头开始覆盖。它包括两个位点,check point和write pos,write pos是写到那个位置了,循环往后递增,check point是当前要擦除的位置。二者中间的空间是可写入的,当write pos追上check point时,就会先停下更新,覆盖掉一些记录,然后继续写入redo log。
redo log 用于保证 crash-safe 能力。innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。这个参数我建议你设置成 1,这样可以保证 MySQL 异常重启之后数据不丢失。
binlog(归档日志,Server层日志)
binlog是逻辑日志,记录了“id=2的记录的c加上1”,用于归档,它又是什么用呢?顾名思义它的主要作用就是归档(还有主从)!有三种模式:
Statement: 记录每一条除了查询之外语句。
Row: 记录每一行记录修改的形式,也就是记录了哪一行改了,改了啥!就比如你update了100条记录,那它就会记录这100条记录改了啥(5.1.5版本才有)。
Mixed:就是statement和row的混合了,由mysql来判断这条语句用哪种形式记录!(5.1.8版本才有)
binlog没有固定大小,每次都是追加记录不会覆盖之前的。
在server层,每个存储引擎都可以使用.
最开始mysql没有InnoDB,也就没有redo log
,也就没有crash-safe能力,binlog
只能用于归档,当InnoDB以插件的形式加入mysql后,需要crash-safe能力,所以就带来了redo log
。
sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数我也建议你设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。
再谈谈为什么需要采用二段式提交,再解答这个问题之前,我们先来看看一条更新语句的执行流程:
可以看到这里redo log的prepare -> commit是二阶段提交事务,现在就可以回答为什么使用两阶段提交了。
为什么要使用二阶段提交呢?
redo log
和binlog
都可以保证事务的提交状态,二阶段提交可以使这两个状态在逻辑上保持一致。
两阶段提交在mysql中:
执行器调用存储引擎更新数据行
存储引擎将数据记入内存中,并写
redo log
,并将redo log
状态设置为prepare
,并返回执行器生成
binlog
,并写入磁盘执行器调用存储引擎的提交事务接口
存储引擎将刚刚的
redo log
状态设置为commit
当在以下阶段发生crash,然后用binlog
进行主备库同步:
完成2后,还未写binlog,所以该条修改不会写到备库里,redo log为prepare状态,在崩溃恢复时发现该事务并没有完成,会回滚,主库和备库都没有该条修改,保持了一致性。
完成3后,写完binlog,该条修改会同步到备库里,redo log仍为prepare状态,在崩溃恢复时发现redo log有prepare状态并且binlog里也存在该条事务并完整,会提交事务,主库和备库都会有该条修改,保持了一致性。
注:崩溃恢复的判断规则
如果redo log里面的事务是完整的,也就是有了commit标识,则提交事务
-
如果redo log里面的事务的prepare是完整的,但没有commit,则判断binlog里是否存在并完整
如果是,则提交事务
如果否,则回滚事务
这里的判断binlog是否完整是通过“binlog是有完整格式”做到的,完整格式包括:
statement格式的最后会有COMMIT
raw格式的最后会有xid event
并且在5.6.2版本后增加了binlog-checksum参数
这里redo log和binlog是通过叫一个xid的共同的数据字段做到的,在崩溃恢复的时候,会按顺序扫描 redo log:
如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;
如果碰到只有 parepare、而没有 commit 的 redo log,就拿着 XID 去 binlog 找对应的事务。
正常运行的实例,最终落盘的修改数据是从redo log过去的还是从buffer pool过去的?
redo log内并没有记录完整的数据页内容,所以它不具有更新完整数据页的能力,所以最终落盘的数据不是从redo log过去的。
如果是正常运行的实例,数据页被修改后,与磁盘的数据页不一致,成为脏页。最终落盘的数据就是将内存中的数据页落盘。
在崩溃恢复场景中,InnoDB 如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将它读到内存,然后让 redo log 更新内存内容。更新完成后,内存页变成脏页,就回到了第一种情况的状态。