二十三讲

80 阅读4分钟

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保证的是:

  • 如果客户端收到事务成功的消息,事务就一定持久化了;

  • 如果客户端收到事务失败(比如主键冲突、回滚等)的消息,事务就一定失败了;

  • 如果客户端收到“执行异常”的消息,应用需要重连后通过查询当前状态来继续后续的逻辑。此时数据库只需要保证内部(数据和日志之间,主库和备库之间)一致就可以了。