0%

WAL保证数据不丢

WAL(write ahead log)机制:先写内存,然后写日志(redo log&binlog),后台有机会将内存的数据写到数据盘。只要redo log和binlog保证持久化到磁盘,就能保证MySQL异常重启后,数据可以恢复。

binlog的写入机制

事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件中。

一个事务的binlog是不能被拆开的,不论这个事务多大,都要确保一次性写入。

执行一条事务所产生的binlog准备写到binlog file时,都会先判断当前文件写入这条binlog之后是否会超过设置的max_binglog_size值,若超过,则rotate自动生成下个binlog file来记录这条binlog信息。
如果单条事务产生的binlog大于max_binlog_size,不会被拆到连个binlog文件,等到这个事务的日志写完再rotate,存在超过配置大小上限的binlog文件。

系统给binlog cache分配了一片内存,每个线程一个,参数binlog_cache_size用于控制单个线程内binlog cache所占内存的大小。如果超过了这个参数规定的大小,就要暂存到磁盘。

事务提交的时候,执行器把binlog cache里的完整事务写入到binlog中,并清空binlog cache。

每个线程都有自己的binlog cache,但是共用同一份binlog文件。

  1. 图中的write,指的是把日志binlog cache写入到文件系统的page cache(文件系统向内核申请的一块内存,缓存读写文件),并没有把数据持久化到磁盘,速度比较快。(此时commit标识完成write,响应客户端,client收到commit成功,主机掉电重启,事务会回滚)
  2. 图中的fsync,指的是将数据持久化到磁盘的操作。一般情况下,fsync才占磁盘的IOPS。

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

  1. sync_binlog=0时,表示每次提交事务都只write,不fsync;(可能会丢失日志)
  2. sync_binlog=1时,表示每次提交事务都会执行fsync;
  3. sync_binlog=N(N>1)时,表示每次提交事务都write,但是累积N个事务后才fsync。(如果主机发生异常重启,会丢失最近N个事务的binlog日志)

出现IO瓶颈的场景里,将sync_binlog设置成一个比较大的值,可以提升性能。常见100~1000。

redo log的写入机制

MySQL-2日志系统

事务在执行过程中,生成的redo log是要先写到redo log buffer对应的内存中,在commit阶段,一次性写入redo log file。(redo log buffer中的内容,并不是每次生成后都直接持久化到磁盘。但一个还没有提交的事务,其部分日志也有可能被提前持久化到磁盘。)

如果事务执行期间MySQL发生异常重启,那这部分日志就丢了。但由于事务并没有提交,这时日志丢了也不会有损失。

redo log可能存在三种状态:

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

日志写到redo log buffer是很快的,wirte到page cache也差不多,但是持久化到磁盘的速度较慢。

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

  1. =0时,表示每次事务提交时都只是把redo log留在redo log buffer中;(MySQL本身异常重启就会丢数据)
  2. =1时,表示每次事务提交时都将redo log直接持久化到磁盘;
  3. =2时,表示每次事务提交时都只是把redo log写到page cache。(主机掉电重启才会丢数据)

出现IO瓶颈的场景里,将innodb_flush_log_at_trx_commit设置成2,性能跟设置成0差不多,但是MySQL异常重启时不会丢数据。

其他操作:

  1. InnoDB有一个后台线程,每隔1秒,就会把redo log buffer中的日志,调用write写到文件系统的page cache,然后调用fsync持久化到磁盘。(事务执行中间过程的redo log也是直接写在redo log buffer中的,这些还未提交的事务的redo log也会被后台线程一起持久化到磁盘。checkpoint)
  2. redo log buffer占用的空间即将达到innodb_log_buffer_size一半的时候,后台线程会主动写盘。这个事务并没有提交,这个写盘动作只是write,而没有调用fsync,也就是只留在了文件系统的page cache。
  3. 并行的其他事务提交的时候,顺带将这个事务的redo log buffer持久化到磁盘(参数设置=1)。(事务A执行到一半,已经写了一些redo log到buffer中,这时事务B提交,就会带上事务A在redo log buffer里的日志一起全部持久化到磁盘。)

两阶段提交

两阶段提交(事务提交时,才会走到事务的redo log prepare阶段—属于事务提交的一个阶段),时序上redo log先prepare,再写binlog,最后再把redo log commit。(事务提交过程中的最后一个步骤,这个步骤执行完成后,这个事务就提交完成了。)

如果innodb_flush_log_at_trx_commit=1,那么redo log在提交的prepare阶段就会持久化一次(崩溃恢复逻辑之一是要依赖于prepare的redo log,再加上binlog来恢复的)。

每秒一次后台轮询刷盘,再加上崩溃恢复这个逻辑,InnoDB就认为redo log在commit的时候就不需要fsync了,只write到文件系统的page cache中就足够了。

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

“非双1”配置:innodb_flush_logs_at_trx_commit=2;sync_binlog=1000;

  • 业务高峰期。
  • 备库延迟,为了让备库尽快赶上主库。(追上后改回双1)
  • 用备份恢复主库的副本,应用binlog的过程。
  • 批量导入数据的时候。

组提交优化

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

LSN也会写到InnoDB的数据页中,来确保数据页不会被多次执行重复的redo log。(crash后恢复)

一次组提交里面,组员越多,节约磁盘IOPS的效果越好。(单线程则只能一个事务对应一次持久化操作)

在并发更新场景下,第一个事务写完redo log buffer以后,接下来这个fsync越晚调用,组员可能越多,节约IOPS的效果就越好。

MySQL通过“拖时间”,让一次fsync带更多的组员。

组提交优化两阶段提交

写binlog也是分成两步的:

  1. 先把binlog从binlog cache中写到磁盘上的binlog文件(文件系统的page cache);(write)(此时commit标识完成write,但没有落盘,就响应客户端,client收到commit成功,主机掉电重启,事务会回滚,数据会丢失)
  2. 调用fsync持久化到磁盘。(fsync)

多个事务的binlog写完了也可以一起持久化(组提交),进一步减少IOPS消耗:

  1. binlog_group_commit_sync_delay参数,表示延迟多少微妙后才调用fsync;
  2. binlog_group_commit_sync_no_delay_count参数,表示累积多少次以后才调用fsync。

只要有一个条件满足就会调用fsync。(当delay=0时,no_delay_count就无效了)。(sync_binlog也受组提交参数影响,sync_delay和sync_no_delay_count的逻辑先走,等满足这两个条件之一,就进入sync_binlog阶段,如果sync_binlog=0,就直接跳过,不调fsync不刷盘)


WAL预写日志机制

WAL机制减少磁盘写,主要得益于两个方面:

  1. redo log和binlog都是顺序写,磁盘的顺序写比随机写速度要快;
  2. 组提交机制,可以大幅度降低磁盘的IOPS消耗。

MySQL出现IO性能瓶颈解决方案:

  1. 设置binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay_count参数,减少binlog的写盘次数。(基于“额外的故意等待”实现,可能会增加语句的响应时间,但没有丢失数据的风险)
  2. 将sync_binlog设置为大于1的值(100-1000)。存在主机掉电时会丢binlog日志的风险。
  3. 将innodb_flush_log_at_trx_commit设置为2。存在主机掉电时丢数据的风险。

binlog cache是每个线程自己维护的,redo log buffer是全局共用的。

  • binlog是不能“被打断的”,一个事务的binlog必须连续写,因此要整个事务完成后,再一起写到文件里。(连续性是write的时候保证的)

    一个线程只能同时有一个事务在执行。每当执行一个begin/start transaction时,就会默认提交上一个事务,如果一个事务的binlog被拆开,在备库执行就会被当做多个事务分段进行,破坏了原子性。

  • redo log没有连续的要求,中间生成的日志可以写到redo log buffer中。redo log buffer中的内容还可以“搭便车”,其他事务提交的时候可以被一起写到磁盘中。

  • binlog存储是以statement或者row格式存储的,而redo log是以page页格式存储的。page格式,天生就是共有的,而row格式只跟当前事务相关。