binlogd的写入方式与参数
binlog cache 分配了一片内存,每个线程一个,参数 binlog_cache_size 用于控制单个线程内 binlog cache 所占内存的大小。如果超过了这个参数规定的大小,就要暂存到磁盘。
事务提交的时候,执行器把 binlog cache 里的完整事务写入到文件系统的page catche中,并清空 binlog cache,然后在合适的机会进行fsync,即将数据持久化到磁盘中。
write阶段,指的就是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快。
fsync阶段,将数据持久化到磁盘的操作。一般情况下,我们认为 fsync 才占磁盘的 IOPS。
write 和 fsync 的时机,是由参数 sync_binlog 控制的:
- sync_binlog=0 的时候,表示每次提交事务都只 write,不 fsync;
- sync_binlog=1 的时候,表示每次提交事务都会执行 fsync;
- sync_binlog=N(N>1) 的时候,表示每次提交事务都 write,但累积 N 个事务后才 fsync。
因此,在出现 IO 瓶颈的场景里,将 sync_binlog 设置成一个比较大的值,可以提升性能。在实际的业务场景中,考虑到丢失日志量的可控性,一般不建议将这个参数设成 0,比较常见的是将其设置为 100~1000 中的某个数值。
但是,将 sync_binlog 设置为 N,对应的风险是:如果主机发生异常重启,会丢失最近 N 个事务的 binlog 日志。
redo log 的写入机制
redoLog的写过程
- 存在 redo log buffer 中,物理上是在 MySQL 进程内存中;
- 写到磁盘 (write),但是没有持久化(fsync),物理上是在文件系统的 page cache 里面;
- 持久化到磁盘,对应的是 hard disk;
日志写到 redo log buffer 是很快的,wirte 到 page cache 也差不多,但是持久化到磁盘的速度就慢多了。
为了控制 redo log 的写入策略,InnoDB 提供了 innodb_flush_log_at_trx_commit 参数,它有三种可能取值:
- 0:表示每次事务提交时都只是把 redo log 留在 redo log buffer 中 ;
- 1:表示每次事务提交时都将 redo log 直接持久化到磁盘;
- 2:表示每次事务提交时都只是把 redo log 写到 page cache。
InnoDB 有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 write 写到文件系统的 page cache,然后调用 fsync 持久化到磁盘。
注意,事务执行中间过程的 redo log 也是直接写在 redo log buffer 中的,这些 redo log 也会被后台线程一起持久化到磁盘。也就是说,一个没有提交的事务的 redo log,也是可能已经持久化到磁盘的。
除了后台线程每秒一次的轮询操作外,还有两种场景会让一个没有提交的事务的 redo log 写入到磁盘中。
- 一种是,redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动写盘。注意,由于这个事务并没有提交,所以这个写盘动作只是 write,而没有调用 fsync,也就是只留在了文件系统的 page cache。
- 另一种是,并行的事务提交的时候,顺带将这个事务的 redo log buffer 持久化到磁盘。假设一个事务 A 执行到一半,已经写了一些 redo log 到 buffer 中,这时候有另外一个线程的事务 B 提交,如果 innodb_flush_log_at_trx_commit 设置的是 1,那么按照这个参数的逻辑,事务 B 要把 redo log buffer 里的日志全部持久化到磁盘。这时候,就会带上事务 A 在 redo log buffer 里的日志一起持久化到磁盘。
mysql redoLog的组提交机制
日志逻辑序列号(log sequence number,LSN):单调递增的,用来对应 redo log 的一个个写入点。每次写入长度为 length 的 redo log, LSN 的值就会加上 length。
多个事务之间的redo log可以按组一次写入,并且对单个事务中redolog和binlog的fsync也放到一起,这样减充分减少 IOPS 的消耗。
binlog 组提交的两个参数
想提升 binlog 组提交的效果,可以通过设置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count 来实现。
binlog_group_commit_sync_delay 参数,表示延迟多少微秒后才调用 fsync;
binlog_group_commit_sync_no_delay_count 参数,表示累积多少次以后才调用 fsync。
这两个条件是或的关系,也就是说只要有一个满足条件就会调用 fsync。
所以,当 binlog_group_commit_sync_delay 设置为 0 的时候,binlog_group_commit_sync_no_delay_count 也无效了。
WAL 机制是减少磁盘写,可是每次提交事务都要写 redo log 和 binlog,这磁盘读写次数也没变少呀?
现在你就能理解了,WAL 机制主要得益于两个方面:
redo log 和 binlog 都是顺序写,磁盘的顺序写比随机写速度要快;
组提交机制,可以大幅度降低磁盘的 IOPS 消耗。
mysql IO性能瓶颈的解决
MySQL 现在出现了性能瓶颈,而且瓶颈在 IO 上,可以通过哪些方法来提升性能呢?可以考虑以下三种方法:
设置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count 参数,减少 binlog 的写盘次数。这个方法是基于“额外的故意等待”来实现的,因此可能会增加语句的响应时间,但没有丢失数据的风险。
将 sync_binlog 设置为大于 1 的值(比较常见是 100~1000)。这样做的风险是,主机掉电时会丢 binlog 日志。
将 innodb_flush_log_at_trx_commit 设置为 2。这样做的风险是,主机掉电的时候会丢数据。
不建议你把 innodb_flush_log_at_trx_commit 设置成 0。因为把这个参数设置成 0,表示 redo log 只保存在内存中,这样的话 MySQL 本身异常重启也会丢数据,风险太大。而 redo log 写到文件系统的 page cache 的速度也是很快的,所以将这个参数设置成 2 跟设置成 0 其实性能差不多,但这样做 MySQL 异常重启时就不会丢数据了,相比之下风险会更小。
在什么场景下将sync_binlog和innodb_flush_log_trx_commit改成“非双 1”?
双1含义:
- sync_binlog为1,说明每次事务提交,binglog都会进行fsync
- innodb_flush_log_trx_commit为1,每次事务提交,redoLog都会进行fsync
非双1(降低IO性能消耗)的场景:
- 业务高峰期。一般如果有预知的高峰期,DBA 会有预案,把主库设置成“非双1”。
- 备库延迟,为了让备库尽快赶上主库。
- 用备份恢复主库的副本,应用 binlog 的过程,这个跟上一种场景类似。
- 批量导入数据的时候。
一般情况下,把生产库改成“非双 1”配置,是设置 innodb_flush_logs_at_trx_commit=2、sync_binlog=1000。