MySQL 23 MySQL是怎么保证数据不丢的?
技术分享
8个月前 (08-03)
0
999+
只要redo log和binlog保证持久化到磁盘,就能确保MySQL异常重启后,数据可以恢复。本文讲讲MySQL写入binlog和redo log的流程。
binlog的写入机制
binlog的写入逻辑比较简单:事务在执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件。
一个事务的binlog不能拆开,因此不论这个事务多大,也要确保一次性写入,这涉及到binlog cache的保存问题。系统给binlog cache分配了一片内存,每个线程一个,参数binlog_cache_size用于控制单个线程内binlog cache所占内存大小,如果超过参数大小,就要暂存到磁盘。
事务提交时,执行器把binlog cache里的完整事务写入binlog并清空binlog cache。
上图中:
write和fsync的时机,由参数sync_binlog控制:
-
sync_binlog=0,表示每次提交事务都只write,不fsync;
-
sync_binlog=1,表示每次提交事务都会fsync;
-
sync_binlog=N>1,表示每次提交事务都write,但累积N个事务后才fsync。
因此,在IO瓶颈的场景里,一般将参数设置为较大的值。在实际业务场景中,考虑到丢失日志量的可控性,比较常见的设置为100-1000中的某个数值。其风险是如果主机发生异常重启,会丢失最近N个事务的binlog日志。
redo log的写入机制
事务在执行过程中,生成的redo log先写到redo log buffer。redo log buffer里的内容,不需要每次生成后都直接持久化到磁盘,但也有可能在事务还没提交的时候被持久化到磁盘。
这个问题,需要从redo log可能存在的三种状态说起:
三种状态为:
-
存在redo log buffer,物理上是在MySQL进程内存,即图中红色;
-
写到磁盘,但是没有持久化(fsync),物理上是在文件系统的page cache,即图中黄色;
-
持久化到磁盘,对应的是hard disk,即图中绿色。
日志写到redo log buffer,write到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有一个后台线程,每隔一秒会把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;
-
并行的事务提交的时候,顺带将这个事务的redo log buffer持久化到磁盘。假设一个事务A执行到一半,已经写了一些redo log到buffer,这时另一个线程的事务B提交,如果innodb_flush_log_at_trx_commit=1,那么事务B要把redo log buffer里的日志全部持久化到磁盘,这时会带上事务A在redo log buffer里的日志一起持久化。
两阶段提交的时序是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。
通常说MySQL的双1配置,指的就是sync_binlog和innodb_flush_log_at_trx_commit都设置为1,即一个事务完整提交前,需要等待两次刷盘,一次是redo log prepare,一次是binlog。
这时,可能有一个疑问,这意味着从MySQL看到的TPS是每秒两万的话,每秒就会写四万次磁盘,但磁盘能力也就两万左右,怎么实现两万的TPS。
解释该问题需要用到组提交机制。先介绍日志逻辑序列化(LSN)的概念。LSN是单调递增的,用来对应redo log的一个个写入点,每次写入长度为length的redo log,LSN的值就会加上length。LSN也会写到InnoDB数据页,来确保数据页不会被多次执行重复的redo log。
下图是三个并发事务在prepare阶段,都写完redo log buffer持久化到磁盘的过程,对应的LSN分别是50、120和160:
所以一次组提交里,组员越多,节约磁盘IOPS的效果越好。在并发更新场景下,第一个事务写完redo log buffer后,接下来fsync越晚调用,组员可能越多,节约IOPS的效果就越好。