数据可靠性

WAL(Write-Ahead Loggin)机制

WAL 是预写式日志, 关键点在于先写日志再写磁盘,也可以说是日志先行。

MySQL在对数据页进行修改时, 通过将"修改了什么"这个操作记录在日志中, 而不必马上将更改内容刷新到磁盘上, 从而将随机写转换为顺序写, 提高了性能。

这种机制一方面提高了MySQL的吞吐量,另一方面也实现了数据的可靠性。

binlog的写入机制

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

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

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

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

图片来自极客时间

可以看到,每个线程有自己binlog cache,但是共用同一份binlog文件。

图中的write,指的就是指把日志写入到文件系统的page cache,并没有把数据持久化到磁盘,所以速度比较快。

图中的fsync,才是将数据持久化到磁盘的操作。

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

先说说说redo log的三种状态:

redo log

  • 红色:存在redo log buffer中,物理上是在MySQL进程内存中
  • 黄色:写到磁盘(write),但是没有持久化(fsync),物理上是在文件系统的page cache里面
  • 绿色:持久化到磁盘,对应的是hard disk

为了控制 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 写入到磁盘中。

  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 在 redo log buffer 里的日志一起持久化到磁盘。

既然MySQL能保证redo log和binlog能可靠性写入磁盘,那么在他们两者直接如何保证可靠转换的呢?

两阶段提交

redo log 先 prepare,再写 binlog,最后再把 redo log commit。利用这个两阶段提交机制,MySQL保证了redo log和binlog的可靠传输。

一图胜千言:

持久化

配置

  • innodb_flush_log_at_trx_commit=1。表示每次事务的redolog都直接持久化到磁盘,保证mysql重启后数据不丢失。
  • sync_binlog=1。表示每次事务的binlog都直接持久化到磁盘,保证mysql重启后binlog记录是完整的。

参考

关注和赞赏都是对小欧莫大的支持! 🤝 🤝 🤝
公众号