mysql buffer pool, redo log, bin log

213 阅读5分钟

链接:

www.bilibili.com/video/BV1Pv…

segmentfault.com/a/119000002…

juejin.cn/post/698755…

www.cnblogs.com/wxlevel/p/1…

juejin.cn/post/684516…

jishuin.proginn.com/p/763bfbd67…

www.cnblogs.com/ZhuChangwu/…

redo log

redo log 包括两个部分,一个是内存中的redo log buffer,另一个是磁盘上的redo log file。

当mysql每执行一条DML语句,就将记录写到 redo log buffer,后续某个时间点再一次性将多个记录写到redo log file中,这种先写buffer,再写磁盘的技术就是MySQL中的WAF(Write-Ahead Logging)技术。

  1. 更新buffer pool中页中的数据
  2. 生成一个redo log
  3. commit,持久化这个redo log

为什么要多一个持久化redo log的过程而不是在commit时候,直接持久化buffer pool里面的页数据呢?
因为设计到了随机IO和顺序IO。
有以下两种情况:

  1. 只修改了页中一条数据,但是持久化这个页却要操作整个页,额外操作了很多数据
  2. 页里的数据只是逻辑上是顺序的,而在磁盘上却不是连续的,则这些数据写的时候属于随机IO,速度肯定会慢

MySQL提前在磁盘开辟了多个连续空间,这些每个连续空间就是一个redo log file,所以持久化redo log的时候,就是顺序IO,速度肯定会快些.
redo log file采用了循环写的方式,redo log大小和数量可以配置,从第一个文件写,写满最后一个文件后(就是write pos追上check point时),会触发脏数据持久化,并推动check point向前移动,此时写入不了新数据

image.png

在操作系统中,用户空间下缓冲区数据一般无法直接写到磁盘里,中间必须经过操作系统内核空间缓冲区(OS Buffer)。所以,redo log buffer写入redo log file实际上是先写入OS Buffer,再通过系统调用fsync()将数据刷到redo log file.

image.png

MySQL有三中将redo log buffer写入redo log file的方式,可以通过参数innodb_flush_log_at_trx_commit配置,详情如下:

参数值含义
0事务提交时不会将 redo log buffer 中日志写入到 os buffer ,而是每秒写入 os buffer 并调用 fsync() 写入到 redo log file 中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据。
1事务每次提交都会将 redo log buffer 中的日志写入 os buffer 并调用 fsync() 刷到 redo log file 中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能较差。
2每次提交都仅写入到 os buffer ,然后是每秒调用 fsync() 将 os buffer 中的日志写入到 redo log file 

注意: 方式2的话,就算MySQL crash了,OS Buffer的数据还在,因为MySQL crash影响不到OS Buffer

binlog

首先要知道什么是 crash-safe?

crash-safe只MySQL宕机重启后,能保证

  • 所有已经提交的事务的数据仍然存在
  • 所有未提交的事务的数据自动回滚

以前MySQL只有MyISAM引擎,但是MyISAM没有crash-safe的能力,并且binlog只能用于归档。后来出现了InnoDB引擎,并且带有redo log日志,而binlogredo log结合,就可以实现crash-safe,并且通过两阶段提交来保证两个日志的一致性

redo log和bin log区别

redologbin log
文件大小固定,可以配置可以配置
实现方式InnoDB引擎实现的binlog是MySQL Server层实现的,所有引擎都可以用
记录方式循环写追加写入,到达一定大小切换到下一个继续写,不会覆盖之前的数据
适用场景崩溃恢复(crash-safe)主从复制和数据恢复

两阶段提交

假设有以下语句:

update table set a=1 where id =1;

流程如下:

image.png 可看到共三个步骤:

  1. 写入redo log,处于prepare状态
  2. 写入bin log
  3. 修改redo log状态为commit

redo log分为preparecommit状态,所以称为两阶段提交

为啥要两阶段提交?

假设a的值原来是100update写完第一个日志后,数据库就crash了,会出现以下情况:

  1. 先写redo logbin log还没有写,MySQL宕机重启,而通过redo log可以把数据恢复过来,所以恢复后,a的值为1.但是由于bin log还没来得及写,所以bin log中没有记录update这个语句,而以后在别处用这个bin log恢复数据库时候,会缺少了一次更新,恢复出来a的值是之前的100.
  2. 先写bin logredo log还没写就宕机重启,导致这个事务无效,所以数据库重启后恢复数据完,a``的值还是100.但是以后用bin log来加载备份数据时候,a的值却是1,相当于bin log多了一条更新语句

两阶段提交的主要用意是:为了保证redolog和binlog数据的安全一致性。只有在这两个日志文件逻辑上高度一致了。你才能放心地使用redolog帮你将数据库中的状态恢复成crash之前的状态,使用binlog实现数据备份、恢复、以及主从复制。而两阶段提交的机制可以保证这两个日志文件的逻辑是高度一致的。没有错误、没有冲突。

那么崩溃恢复如何完成呢?

如下图:

image.png

  • 如果redo log里面的事务是commit的,则直接提交
  • 如果crash时刻A,由于redo log未提交,bin log还没写入,这个事务会回滚
  • 如果crash时刻B,则会判断bin log中对应的事务是否存在,存在则提交,否则回滚

一定需要两阶段提交吗?

bin log默认是不开启的,因为如果你不需要bin log的特性(数据备份恢复,主从同步),就不需要两阶段提交,就用不到bin log了。
只用redo log就可以在crash后将MySQL内存中的数据恢复到crash之前的状态