前言

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(预写日志)策略,即当事务提交时先写重做日志,然后再择时将脏页写入磁盘

mysql中nvl的_mysql


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的写入机制大致入下图所示。

mysql中nvl的_mysql中nvl的_02


显然对性能的影响是随着持久化程度的增加而增加的。通常我们建议在日常场景将该值设置为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能够安全的写入文件而不会覆盖最近的检查点。