前言
InnoDB 有两块非常重要的日志,一个是undo log
,它用来保证事务的原子性
以及InnoDB的MVCC
,另外一个就是是redo log
,它用来保证事务的持久性
。
InnoDB记录了对数据文件的物理更改
,并保证总是日志先行
,也就是所谓的WAL,即在持久化数据文件前,保证之前的redo日志已经写到磁盘。
LSN
(log sequence number) 用于记录日志序号
,它是一个不断递增的 unsigned long long 类型整数。在 InnoDB 的日志系统中,LSN 无处不在,它既用于表示修改脏页时的日志序号
,也用于记录checkpoint,通过LSN,可以具体的定位
到其在redo log文件
中的位置。
为了管理脏页,在 Buffer Pool
的每个instance
上都维持了一个flush list
,flush list 上的 page 按照修改这些 page 的LSN号进行排序。因此定期做redo checkpoint点时,选择的 LSN 总是所有 bp instance 的 flush list 上最老的那个page(拥有最小的LSN)。由于采用WAL的策略,每次事务提交时需要持久化 redo log 才能保证事务不丢。而延迟刷脏页则起到了合并多次修改的效果,避免频繁写数据文件造成的性能问题。
本文的分析基于最新的MySQL 5.7.7-RC版本。
Redo log
InnoDB的redo log可以通过参数innodb_log_files_in_group
配置成多个文件,另外一个参数innodb_log_file_size
表示每个文件的大小。
因此总的redo log大小为innodb_log_files_in_group * innodb_log_file_size。
Redo log文件以ib_logfile[number]命名
,日志目录
可以通过参数innodb_log_group_home_dir
控制。Redo log 以顺序
的方式写入文件
,写满时
则回溯到第一个文件
,进行覆盖写
。(但在做redo checkpoint时,也会更新第一个日志文件的头部checkpoint标记,所以严格来讲也不算顺序写)。在覆盖写之前
,总是要保证对应的脏页
已经刷到了磁盘
。在非常大的负载
下,Redo log可能产生的速度非常快,导致频繁的刷脏操作
,进而导致性能下降
,通常在redo checkpoint的日志超过文件总大小
的76%
之后,InnoDB 认为这可能是个不安全的点,会强制的preflush脏页
,导致大量用户线程停住
。如果可预期会有这样的场景:
建议
调大redo log文件
的大小。可以做一次干净的shutdown
,然后修改Redo log配置
,重启
实例
Redo Log 如果要存储数据
则先存储数据的日志
,如果发生宕机导致数据丢失,就通过重做日志进行数据恢复。重做日志保证了数据的可靠性
,InnoDB采用了Write Ahead Log(预写日志)
策略,即当事务提交时
,先写重做日志
,然后再择时
将脏页写入磁盘
。
InnoDB存储引擎会首先将数据变更
的重做日志信息先放入重做日志缓冲中
,然后再按照一定频率将其刷新到重做日志文件。重做日志缓冲一般不需要设置得很大,因为一般情况每一秒钟都会讲重做日志缓冲刷新到日志文件中。可通过配置参数innodb_log_buffer_size
控制,默认为8MB
。
Redo 写盘操作
我们所熟悉的参数innodb_flush_log_at_trx_commit
作用于事务提交时,这也是最常见的场景:
- 当设置该值为
1
时,每次事务提交都要做一次fsync
,这是最安全的配置,即使宕机也不会丢失事务 - 当设置为
2
时,则在事务提交时只做write操作,只保证写到系统的page cache
,因此实例crash不会丢失事务,但宕机则可能丢失事务 - 当设置为
0
时,事务提交不会触发redo写操作,而是留给后台线程每秒一次的刷盘操作
,因此实例crash将最多丢失1秒钟内的事务
InnoDB的写入机制
大致入下图所示。
显然对性能的影响是随着持久化程度的增加而增加的。通常我们建议在日常
场景将该值设置为1
,但在系统高峰期临时修改成2
以应对大负载。
由于各个事务
可以交叉
的将事务日志拷贝到log buffer
中,因而一次事务提交
触发的写
redo到文件
,可能
隐式的帮别的线程“顺便”也写了redo log
,从而达到group commit
的效果。
Redo checkpoint
check point目的
是为了定期
将data page buffer
的内容转储到data file
。在转储时,会记录check point
发生的”时刻“
。在故障恢复
时候,只需要redo/undo最近的一次checkpoint之后的操作
。
InnoDB的redo log采用覆盖循环写
的方式,而不是
拥有无限
的redo空间
;即使拥有理论上极大的redo log空间,为了
从崩溃中快速恢复
,及时做checkpoint
也是非常有必要的
。
在innodb中,数据刷盘的规则只有一个:checkpoint。innodb存储引擎中checkpoint分为两种:
-
sharp checkpoint
:在重用redo log文件
(例如切换日志文件
)的时候,将所有已记录
到redo log中对应的
脏数据刷到磁盘。 fuzzy checkpoint
:一次只刷一小部分
的日志到磁盘,而非将所有脏日志刷盘。
有以下几种情况会触发该检查点:
-
master thread checkpoint
:由master线程控制,每秒或每10秒
刷入一定比例
的脏页到磁盘。 -
flush_lru_list checkpoint
:从MySQL5.6
开始可通过innodb_page_cleaners
变量指定专门负责脏页刷盘的page cleaner线程的个数
,该线程的目的
是为了保证lru列表有可用的空闲页
。 -
async/sync flush checkpoint
:同步刷盘
还是异步刷盘
。例如还有非常多的脏页
没刷到磁盘(非常多是多少,有比例控制
),这时候会选择同步
刷到磁盘,但这很少出现;如果脏页不是很多
,可以选择异步
刷到磁盘,如果脏页很少
,可以暂时不刷
脏页到磁盘 -
dirty page too much checkpoint
:脏页太多
时强制触发
检查点,目的是为了保证缓存有足够的空闲空间
。too much的比例由变量innodb_max_dirty_pages_pct
控制,MySQL 5.6默认
的值为75
,即当脏页占缓冲池的百分之75后,就强制刷一部分脏页到磁盘。
由于刷脏页需要一定的时间来完成,所以记录检查点的位置
是在每次刷盘结束之后
才在redo log中标记
的。
InnoDB的master线程
大约每隔10秒
会做一次redo checkpoint
,但不会去preflush脏页来推进checkpoint点。
通常普通的低压力负载下,page cleaner
线程的刷脏速度足以保证可作为检查点的lsn被及时的推进。但如果系统负载很高时,redo log推进速度过快
,而page cleaner来不及刷脏,这时候就会出现用户线程陷入同步刷脏并做checkpoint的境地
,这种策略的目的是为了保证redo log能够安全的写入文件而不会覆盖最近的检查点。