mysql 的两个重要的日志模块
mysql查询语句的执行过程
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”,用于归档,在server层,每个存储引擎都可以使用
最开始mysql没有InnoDB,也就没有redo log,也就没有crash-safe能力,binlog只能用于归档,当InnoDB以插件的形式加入mysql后,需要crash-safe能力,所以就带来了redo log
二者很重要的不同:
- redo log是物理日志,记录的是哪个数据页做了什么修改;binlog是逻辑日志,记录的是语句的原始逻辑,比如“id=5的c字段加1”
- redo log是循环写的,binlog是可以追加写入的,写到一定大小会写下一个,不会覆盖以前的日志
sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数我也建议你设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。
一条更新语句的执行流程
- 客户端发来update t set c = c + 1 where id = 2的update语句
- 经过前面的分析优化流程后,执行器调用存储引擎拿到id=2的记录
- 拿到后进行运算字段c得到新数据行
- 调用执行器将数据行写入
- 存储引擎将数据行更新到内存中,并写redo log,并且redo log的状态为prepare,返回到执行器
- 执行器根据语句生成binlog,并写入磁盘
- 执行器调用提交事务接口,将刚刚的redo log状态设置为commit,更新就完成了
可以看到这里redo log的prepare -> commit是二阶段提交事务
DBA为何能承诺能够恢复数据库到当前时间半个月内的任意时间点的状态呢?
可以通过binlog重放+定期备份整库来达到。
首先,我们会根据数据的重要性做周期性的整库备份
当出现进行恢复到任何一个时间的需求时,可以这么做:
- 找到在需求时间点之前并且离需求时间点最近的一份整库备份
- 以此来重建临时库
- 重放整库备份时间到需求时间点之间的binlog逻辑操作即可
为什么要使用二阶段提交呢?
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 更新内存内容。更新完成后,内存页变成脏页,就回到了第一种情况的状态。