redo和binlog的写入机制

只要 redo log 和 binlog 保证持久化到磁盘,就能确保 MySQL 异常重启后,数据可以恢复。再一起看看 MySQL 写入 binlog 和 redo log 的流程
一、redolog的写入机制

前面我们提到,事务在执行过程中,redolog会不断写入。在此过程中生成的 redolog 是要先写到 redo log buffer 的。

在一个事务的更新过程中,日志是要写多次的。比如说下面这个事务:这个事务要往两个表中插入记录,插入数据的过程中,生成的日志都得先保存起来

begin;
insert into t1 ...
insert into t2 ...
commit;

所以, redo log buffer 就是一块内存,用来先存 redo 日志的,且redolog buffer中的内容,每次生成后不用直接持久化到磁盘。

也就是说,在执行第一个 insert 的时候,数据的内存被修改了, redo log buffer 也写入了日志。

因为redo log buffer在内存中,如果事务执行期间 MySQL 发生异常重启,那这部分redo日志就丢了。但是此时事务并没有提交,所以就算日志丢了,也不会有什么损失

虽然我们说,redo log buffer中的内容,每次生成后不用直接持久化到磁盘中。但是事务还没有提交的时候,redo log buffer中的部分日志还是有可能被持久化到磁盘中的

这就要从redo log可能存在的三种状态说起,对应着下图的三种颜色

redo和binlog的写入机制_第1张图片

redo log的三种状态分别是:

1.  存在 redo log buffer 中,物理上是在 MySQL 进程内存中,就是图中的红色部分;
2.  写到磁盘 (write) ,但是没有持久化( fsync) ,物理上是在文件系统的 page cache 里面,也就是图中色黄色部分

3.  持久化到磁盘,对应的是 hard disk ,也就是图中的绿色部分。

为了控制 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中有一个后台线程,每隔一秒,就会把 redo log buffer 中的日志,调用 write 写到文件系统的page cache ,然后调用 fsync 持久化到磁盘。这就是后台线程的轮询操作

实际上,除了后台线程每秒一次的轮询操作外,还有两种场景会让一个没有提交的事务的 redolog 写入到磁盘中。

1.  一种是, redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动写盘。这个写盘动作只是 write ,而没有调用 fsync ,也就是只留在了文件系统的 page cache中。

2.  另一种是,并行的事务提交的时候,顺带将这个事务的 redo log buffer 持久化到磁盘。假设一个事务 A 执行到一半,已经写了一些 redo log 到 buffer 中,这时候有另外一个线程的事务 B 提交,如果 innodb_flush_log_at_trx_commit 设置的是 1 ,那么按照这个参数的逻辑,事务 B 要把 redo log buffer 里的日志全部持久化到磁盘。这时候,就会带上事务 A 在 redolog buffer 里的日志一起持久化到磁盘。

我们经常把innodb_flush_log_at_trx_commit 设置成 1,那么 redo log 在 prepare 阶段就要持久化一次(因为所谓的 redo log prepare ,是 “ 当前事务提交 ” 的一个阶段)因为有一个崩溃恢复的判断逻辑是要依赖于 prepare  的 redo log ,再加上 binlog 来恢复的,所以必须把它持久化
二、binlog的写入机制

前面我们说过,区分于redolog的事务执行过程中 会不断的写入,binlog 是在 事务最终提交前 写入的

具体的实现方法就是:事务执行过程中,先把日志写到 binlog cache ,事务提交的时候,再把 binlog cache 写到 binlog 文件中

一个事务的 binlog 是不能被拆开的,因此不论这个事务多大,也要确保一次性写入,这就涉及到了binlog cache保存这个事务binlog 的能力

    系统给 binlog cache 分配了一片内存,每个线程一个,参数 binlog_cache_size 用于控制单个线程内 binlog cache 所占内存的大小

    如果超过了这个参数规定的大小,就要暂存到磁盘,事务提交的时候,把 binlog cache 里的完整事务写入到 binlog 中,并清空 binlog cache

redo和binlog的写入机制_第2张图片

每个线程有自己 binlog cache ( binlog cache 是每个线程自己维护的),但是共用同一份 binlog 文件,保证了一个事务的binlog不能被拆开。binlog的阶段也就三种:

    图中的 write ,指的就是指把日志写入到文件系统的 page cache(是操作系统的文件系统上的,属于操作系统的内存) ,并没有把数据持久化到磁盘,所以速度比较快。
    图中的 fsync ,才是将数据持久化到磁盘的操作(由系统缓冲区写回磁盘的函数)。一般情况下,我们认为 fsync 才占磁盘的IOPS (IOPS (Input/Output Operations Per Second),即每秒进行读写(I/O)操作的次数)

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

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

所以在出现 IO 瓶颈的场景里,将 sync_binlog 设置成一个比较大的值,可以提升性能。但是,如果主机发生异常重启,会丢失最近 N 个事务的binlog 日志,因为一直没有fsync

 

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

    事务执行期间,还没到提交阶段,如果发生 crash 的话,redo log 肯定丢了,这时候 binlog  也还在 binlog cache 里,没发给备库。 crash 以后 redo log 和 binlog都没有了,不会出现主备不一致、数据不一致的现象

三、组提交( group commit )机制

上面我们说的双1配置,这意味着我从 MySQL 看到的 TPS吞吐量 是每秒两万的话,每秒就会写四万次磁盘,然而并不是这样的,要解释就要用到组提交( group commit )机制,可以节约持久化到磁盘的消耗

首先我们引入日志逻辑序列号( log sequence number , LSN )的概念。 LSN 是单调递增的,用来对应 redo log 的一个个写入点,每次写入长度为 length 的 redo log , LSN 的值就会加上 length 。

如图  所示,是三个并发事务 (trx1, trx2, trx3) 在 prepare  阶段,都写完 redo log buffer ,持久化到磁盘的过程

redo和binlog的写入机制_第3张图片

 

1. trx1 是第一个到达的(redo log的第一个写入点),会被选为这组的 leader ;

2.  等 trx1 要开始写盘的时候,这个组里面已经有了三个事务,这时候 LSN 也变成了 160 ;

3. trx1 去写盘的时候,带的就是 LSN=160 ,因此等 trx1 返回时,所有 LSN 小于等于 160 的 redolog ,都已经被持久化到磁盘;

4.  这时候 trx2 和 trx3 就可以直接返回了。

    所以,一次组提交里面,组员越多,节约磁盘 IOPS 的效果越好:第一个事务写完 redo log buffer 以后,接下来这个 fsync 越晚调用,LSN增长越多,组员可能越多。调用fsync时,顺便把fsync了其他事物,节约 IOPS 的效果就越好。

所以,为了让一次 fsync 带的组员更多, MySQL 有一个很有趣的优化:拖时间

实际上,写 binlog 是分成两步的:

1.  先把 binlog 从 binlog cache 中写到磁盘上的 binlog 文件;

2.  调用 fsync 持久化。      

         redo和binlog的写入机制_第4张图片

MySQL 为了让组提交的效果更好,把 redo log 做 fsync 的时间拖到了步骤 1 之后,在redolog的write和fsync阶段中插入了binlog 的write阶段

也就是说,上面的左图变成了右图。这么一来, binlog 也可以组提交了。第 4 步把 binlog fsync 到磁盘时,如果有多个事务
的 binlog 已经写完了,也是一起持久化的
————————————————
版权声明:本文为CSDN博主「hanpeiyu1995」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hanpeiyu1995/article/details/90045391

你可能感兴趣的:(redo和binlog的写入机制)