binlog的写入机制
事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件中。然后清空binlog cache。一个事务的binlog 是不能被拆开的。
系统给binlog cache分配一片内存,每个内存一个(参数binlog_cache_size控制单个线程内的内存大小),超过参数规定大小就要暂存到磁盘。
写入缓存(write)和刷到磁盘(fsync)的时机有参数sync_binlog控制:
- 为0的时候表示每次提交事务都只write,不fsync
- 为1的时候,表示每次提交都会执行fsync
- N(大于1)的时候,表示每次提交事务都write,但累积n个事务后才fsync
在实际的业务场景中,考虑到丢失日志量的可控性,一般不建议将这个参数设成0,比较常见的是将其设置为100~1000中的某个数值。风险是主机发生异常将会丢失最近N个事务的binlog 日志。
redo log 写入机制
重做日志(逻辑),先写入一个重做日志缓冲(redo log buffer)中,然后按照一定的条件顺序写入日志文件。buffer是一个固定大小并且循环写入的内存。
redolog有三种状态:内存中(buffer),写到磁盘但是没有持久化(文件系统中的page cache中),磁盘中。
控制redo log的写入策略 参数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写到文件系统的pagecache,然后调用fsync持久化到磁盘。 在这个过程中可能有些没有提交事务的redo log也会持久化到磁盘。 还有redo log buffer的空间即将达到innodb_log_buffer_size一半的时候,后台线程会主动写盘。另一种场景是事务并行提交的时候,顺带将这个事务的redo log buffer持久化到磁盘。
“双1”配置
通常我们说MySQL的“双1”配置,指的就是sync_binlog和innodb_flush_log_at_trx_commit都设置成 1。也就是说,一个事务完整提交前,需要等待两次刷盘,一次是redo log(prepare 阶段),一次是binlog。
LSN
日志逻辑序列号(log sequence number),LSN是单调递增的,用来对应redo log的一个个写入点。每次写入长度为length的redo log,LSN的值都会递增length。 LSN也会写到innodb的数据页中来确保不会多次执行重复的redo log。
组提交
当一个新事务进行提交的时候这个时候并发进入了其他的事务。在这个事务进行提交时会将并行的其他事务一并提交称为组提交。
在并发更新场景下,第一个事务写完redo log buffer以后,接下来这个fsync越晚调用,组员可能越多,节约IOPS的效果就越好。
如果你想提升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。
如果你的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。这样做的风险是,主机掉电的时候会丢数据。
crash-safe保证
实际上数据库的crash-safe保证的是:
-
如果客户端收到事务成功的消息,事务就一定持久化了;
-
如果客户端收到事务失败(比如主键冲突、回滚等)的消息,事务就一定失败了;
-
如果客户端收到“执行异常”的消息,应用需要重连后通过查询当前状态来继续后续的逻辑。此时数据库只需要保证内部(数据和日志之间,主库和备库之间)一致就可以了。