binlog的写入机制

总的流程:先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件中。

但是需要注意,一个事务的binlog无论多大,都需要一次性写入,这就涉及到binlog cache的保存。

每一个线程都会有一个binlog cache,可以设置其大小。如果超过大小限制,就要暂存在磁盘当中。但是所有的线程共享一个binlog file,也就是说,每个线程的binlog cache,都会存入到同一个文件。

一个具体的例子如下:

image-20230418143649904

上图中的write操作,其实只是把binlog cache的内容写入文件系统的缓存,并没有直接同步到磁盘,而fsync才是真正的将文件系统缓存的内容写入到磁盘上。

write 和fsync的时机,是由参数sync_binlog控制的:

  1. sync_binlog=0的时候,表示每次提交事务都只write,不fsync;
  2. sync_binlog=1的时候,表示每次提交事务都会执行fsync;
  3. sync_binlog=N(N>1)的时候,表示每次提交事务都write,但累积N个事务后才fsync。

redo log的写入机制

redo log 每次写入时,生成的redo log会先写入redo log buffer,而redo log buffer里面的内容并不需要每次写完后都同步到磁盘。

因为事务还没提交时,数据库异常重启,这部分日志会丢失,但是因为事务没提交,所以不会对数据一致性造成影响。因为事务只有在提交时,事务内所作的修改才真正起作用。

但是事务还没提交时,redo log buffer里面的内容也会有刷入磁盘的情况。

redo log可能存在的三种状态

1、存在于redo log buffer中,也就是还在MySQL进程中。

2、写入了文件系统的缓存当中。

3、写入了磁盘当中。

redo log的写入策略

为了控制redo log的写入策略,InnoDB提供了innodb_flush_log_at_trx_commit参数,控制写入策略:

  1. 设置为0的时候,表示每次事务提交时都只是把redo log留在redo log buffer中;
  2. 设置为1的时候,表示每次事务提交时都将redo log直接持久化到磁盘;
  3. 设置为2的时候,表示每次事务提交时都只是把redo log写到page cache。

InnoDB有一个后台线程,每隔1秒,就会把redo log buffer中的日志,调用write写到文件系统的page cache,然后调用fsync持久化到磁盘。

这也就解释了为什么会有没提交事务的redo log也被写入了磁盘当中。因为事务执行时,日志就会先写入redo log buffer中,后台线程会在事务没提交时,将数据刷入磁盘当中。

另外两种会导致事务没提交时,redo log 被写入磁盘:

一种是,redo log buffer占用的空间即将达到 innodb_log_buffer_size一半的时候,后台线程会主动写盘。但这里只是写入文件系统的缓存中,不是写入磁盘。

另一种是,并行的事务提交的时候,顺带将这个事务的redo log buffer持久化到磁盘。比如说事务A执行了一半,它已经写入了一部分日志到redo log buffer中。如果此时另一个事务提交了,而且innodb_flush_log_at_trx_commit参数设置的是1,那么它就会直接将redo log buffer中的内容直接写入磁盘,这样事务A的一部分日志就被写入了磁盘当中。

双一配置

两阶段提交的流程是:时序上redo log先prepare, 再写binlog,最后再把redo log commit。

将innodb_flush_log_at_trx_commit设置为1时,redo log在处于prepare阶段时,就会刷新到磁盘当中。

通常我们说MySQL的“双1”配置,指的就是sync_binlog和innodb_flush_log_at_trx_commit都设置成 1。也就是说,一个事务完整提交前,需要等待两次刷盘,一次是redo log(prepare 阶段),一次是binlog。

参考

《MySQL45讲》