InnoDB存储引擎架构详解

3,540 阅读29分钟

1、MySQL存储引擎

存储引擎是表级别的,同一个数据库中,不同的表可以是不同的存储引擎。

在创建表的时候可以直接指定存储引擎。

mysql> create table user (id int primary key, age int, number int) engine=innodb;
Query OK, 0 rows affected (0.00 sec)
存储引擎说明
InnoDB5.5版本后MySQL的默认数据库,支持事务和行级锁定,比MyISAM处理速度稍慢
MyISAM高速引擎,拥有较高的插入,查询速度,但不支持事务
Memory内存存储引擎,拥有极高的插入,更新和查询效率。但是会占用和数据量成正比的内存空间。只在内存上保存数据,意味着数据可能会丢失
MRG_MyISAM(MERGE)将多个表联合成一个表使用,在超大规模数据存储时很有用
Falcon一种新的存储引擎,支持事物处理,传言可能是InnoDB的替代者
Archive将数据压缩后进行存储,非常适合存储大量的独立的,作为历史记录的数据,但是只能进行插入和查询操作
CSVCSV 存储引擎是基于 CSV 格式文件存储数据(应用于跨平台的数据交换)
  • 查看存储引擎
mysql> show engines;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                        | Transactions | XA   | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         |
| BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         |
| MyISAM             | YES     | MyISAM storage engine                                          | NO           | NO   | NO         |
| CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         |
| ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         |
| FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
9 rows in set (0.00 sec)
  • InnoDB和MyISAM存储引擎区别:
InnoDBMyISAM
存储文件.frm 表定义文件
.ibd 数据文件和索引文件
.frm 表定义文件
.myd 数据文件
.myi 索引文件
表锁、行锁表锁
事务支持不支持
CRDU读、写读多
count扫表专门存储的地方
索引结构B+ TreeB+ Tree
  • 引擎的选择 除非需要用到某些InnoDB不具备的特性,并且没有其他办法可以替代,否则都应该选择InnoDB引擎。

2、InnoDB架构图

InnoDB架构图

从图中可见,InnoDB存储引擎由内存池后台线程磁盘文件三大部分组成。

主要分为内存区域和文件区域:

  • 内存区域:
    • Buffer Pool 缓冲区,放经常使用的数据页和索引页
    • Change Buffer 修改缓冲区,对数据进行增删改操作时,先把数据放到这里
    • Log Buffer 重做日志缓冲区,redo log, 保证数据库不丢失数据的重要环节,只要数据写入redo log成功,则认为更新成功。
  • 文件区域:
    • System Tablespace 系统表空间,ibdata1,记录系统相关的数据
      • InnoDB Data Dictionary:系统表空间的数据字典
      • Doublewrite Buffer:双写缓冲区
      • Change Buffer:修改的数据
      • Undo Logs:回滚日志,可以配置为回滚表空间独立出来
    • File-Per-Table Tablespace 用户表空间,每一个表对应一个表空间(*.ibd文件)存储用户表的数据以及索引
    • Genaral Tablespace 通用表空间,
    • Temporary Tablespace 临时表空间
    • Redo Log 重做日志 ib_logfile0,ib_logfile1

2.1、InnoDB磁盘文件

InnoDB的主要的磁盘文件主要分为三大块:一是系统表空间,二是用户表空间,三是redo日志文件和归档文件。
二进制文件(binlog)等文件是MySQL Server层维护的文件,所以未列入InnoDB的磁盘文件中。

2.1.1、重做日志文件

什么是重做日志文件?

默认情况下,在InnoDB存储引擎的数据目录下会有两个名为ib_logfile0ib_logfile1的文件,这就是InnoDB的重做日志文件(redo log file),它记录了对于InnoDB存储引擎的事务日志。
achieve redo log(重做日志归档)在InnoDB非常古老的版本(MySQL 4.0.6之前的版本)才存在,不过这个功能当时没什么用,所以取消了。自从MySQL 8.0.17发布后,又恢复了redo log归档(redo log archiving)功能。我们学习的版本中5.7.29中没有此功能。

重做日志文件的作用是什么?

对mysql数据完整性一个有力的保障
  • 当InnoDB的数据存储文件发生错误时,重做日志文件就能派上用场。InnoDB存储引擎可以使用重做日志文件将数据恢复为正确状态,以此来保证数据的正确性和完整性。
  • 为了得到更高的可靠性,用户可以设置多个镜像日志组,将不同的文件组放在不同的磁盘上,以此来提高重做日志的高可用性。

重做日志文件组是如何写入数据的?

每个InnoDB存储引擎至少有1个重做日志文件组(group),每个文件组下至少有2个重做日志文件,如默认的ib_logfile0ib_logfile1

重做日志的数量由参数innodb_log_files_in_group控制

mysql> show variables like 'innodb_log_files_in_group';
+---------------------------+-------+
| Variable_name             | Value |
+---------------------------+-------+
| innodb_log_files_in_group | 2     |
+---------------------------+-------+
1 row in set (0.00 sec)

在日志组中每个重做日志文件的大小一致,并以循环写入的方式运行。

InnoDB存储引擎先写入重做日志文件1,当文件被写满时,会切换到重做日志文件2,再当重做日志文件2也被写满时,再切换到重做日志文件1。

如何设置重做日志文件大小?

可以使用innodb_log_file_size来设置重做日志文件的大小(默认是50M),这对InnoDB存储引擎的性能有着非常大的影响。

mysql> show variables like 'innodb_log_file_size';
+----------------------+----------+
| Variable_name        | Value    |
+----------------------+----------+
| innodb_log_file_size | 50331648 |
+----------------------+----------+
1 row in set (0.00 sec)

如果重做日志文件设置的太大,数据丢失时,恢复时可能需要很长的时间;

另一方面,如果设置的太小,重做日志文件太小会导致依据checkpoint的检查需要频繁刷新脏页到磁盘中,导致性能的抖动。

2.1.2、系统表空间和用户表空间

系统表空间存储哪些数据?

系统表空间是一个共享的表空间,因为它是被多个表共享的。

InnoDB系统表空间包含InnoDB数据字典(元数据以及相关对象)、double write buffer、changebuffer、undo logs的存储区域。

系统表空间也默认包含任何用户在系统表空间创建的表数据索引数据

系统表空间配置解析

系统表空间是由一个或者多个数据文件组成。

默认情况下,一个初始大小为10MB,名为ibdata1的系统数据文件在MySQL的data目录下被创建。用户可以使用 innodb_data_file_path 对数据文件的大小和数量进行配置。

innodb_data_file_path 的格式如下:

innodb_data_file_path=datafile1[,datafile2]...
mysql> show variables like 'innodb_data_file_path';
+-----------------------+------------------------+
| Variable_name         | Value                  |
+-----------------------+------------------------+
| innodb_data_file_path | ibdata1:12M:autoextend |
+-----------------------+------------------------+
1 row in set (0.00 sec)

示例(这里将/db/ibdata1和/dr2/db/ibdata2两个文件组成系统表空间):

innodb_data_file_path=/db/ibdata1:1000M;/dr2/db/ibdata2:1000M:autoextend

如果这两个文件位于不同的磁盘上,磁盘的负载可能被平均,因此可以提高数据库的整体性能
两个文件的文件名之后都跟了属性,表示文件ibdata1的大小为1000MB,文件ibdata2的大小为1000MB,而且用完空间之后可以自动增长(autoextend)。

如何使用用户表空间?

如果设置了参数innodb_file_per_table,则用户可以将每个基于InnoDB存储引擎的表产生一个独立的用户表空间。用户表空间的命名规则为:表名.ibd
通过这种方式,用户不用将所有数据都存放于默认的系统表空间中。

用户表空间存储哪些数据?

用户表空间只存储该表的数据索引信息,其余信息还是存放在默认的系统表空间中。

2.1.3、InnoDB逻辑存储结构

InnoDB存储引擎逻辑存储结构可分为五级:表空间、段、区、页、行。

2.1.3.1、表空间

从InnoDB存储引擎的逻辑存储结构看,所有数据都被逻辑地存放在一个空间中,称之为表空间 (tablespace)。

从功能上来看,InnoDB存储引擎的表空间分为系统表空间独占表空间通用表空间临时表空间Undo表空间

如果开启了独立表空间innodb_file_per_table=1,每张表的数据都会存储到一个独立的表空间,即一个单独的.ibd文件。

InnoDB 存储引擎有一个共享表空间,叫做系统表空间,对一个磁盘上的文件名为ibdata1。如果设置了参数innodb_file_per_table=0,关闭了独占表空间,则所有基于InnoDB存储引擎的表数据都会记录 到系统表空间。

2.1.3.2、段

表空间是由各个段组成的,常见的段有数据段、索引段、回滚段等。

如果开启了独立表空间innodb_file_per_table=1,每张表的数据都会存储到一个独立的表空间,即一个单独的.ibd文件。一个用户表空间里面由很多个段组成,创建一个索引时会创建两个段:数据段和索引段

  • 数据段存储着索引树中叶子节点的数据。

  • 索引段存储着索引树中非叶子节点的数据。

一个段的空间大小是随着表的大小自动扩展的:表有多大,段就有多大

一个段会包含多个区,至少会有一个区,段扩展的最小单位是区。

2.1.3.3、区(1M=64个页(16K))

一个区由64个连续的页组成,一个区的大小=1M=64个页(16K)。为了保证区中页的连续性,区扩展时InnoDB 存储引擎会一次性从磁盘申请4 ~ 5个区。

2.1.3.4、页(16KB)

InnoDB 每个页默认大小时是 16KB页是 InnoDB管理磁盘的最小单位,也是InnoDB中磁盘和内存交互的最小单位。

查看页大小的命令:

mysql> show global variables like 'innodb_page_size';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+
1 row in set (0.00 sec)

索引树上一个节点就是一个页,MySQL规定一个页上最少存储2个数据项。如果向一个页插入数据 时,这个页已将满了,就会从区中分配一个新页。如果向索引树叶子节点中间的一个页中插入数据,如果这个页是满的,就会发生页分裂。

操作系统管理磁盘的最小单位也是页,是操作系统读写磁盘最小单位,Linux中页一般是4K,可以通过命令查看。getconf PAGE_SIZE

所以InnoDB从磁盘中读取一个数据页时,操作系统会分4次从磁盘文件中读取数据到内存。写入也是一样的,需要分4次从内存写入到磁盘中。

2.1.3.5、行

行中存储的就是表中的数据行。每个页存储行的数量取决于表结构的定义。

InnoDB的数据是以行为单位存储的,1个页中包含多个行。在MySQL5.7中,InnoDB提供了4种行格 式:Compact、Redundant、Dynamic和Compressed行格式,Dynamic为MySQL5.7默认的行格式。

创建表时可以指定行格式:

CREATE TABLE t1 (c1 INT) ROW_FORMAT=DYNAMIC; 
ALTER TABLE tablename ROW_FORMAT=行格式名称; 
#修改默认行格式 
SET GLOBAL innodb_default_row_format=DYNAMIC; 
#查看表行格式 
SHOW TABLE STATUS LIKE 'student'\G;

3、InnoDB内存结构

3.1、Buffer Pool缓冲池

包含数据页和索引页

为了提高查询性能和读写性能,在内存做了一个磁盘的映射,将磁盘上的使用频率最高的(基于LRU算法)的数据页和内存也预读一部分到内存的缓冲池里边。

3.1.1、概述

InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。但是由于CPU速度和磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池记录来提高数据库的的整体性能

所以,缓冲池的大小直接影响着数据库的整体性能,可以通过配置参数nnodb_buffer_pool_size来设置。

具体来看,缓冲池中缓存的数据页类型有:

  • 索引页、
  • 数据页、
  • undo页、
  • 插入缓冲(insert buffer)、
  • 自适应哈希索引(adaptive hash index)、
  • InnoDB存储的锁信息(lock info)
  • 数据字典信息(data dictionary)。

在架构图上可以看到,InnoDB存储引擎的内存区域除了有缓冲池之外,还有重做日志缓冲和额外内存池。InnoDB存储引擎首先将重做日志信息先放到这个缓冲区中,然后按照一定频率将其刷新到重做日志文件中。重做日志缓冲一般不需要设置的很大,该值可由配置参数 innodb_log_buffer_size 控制。

3.1.2、数据页和索引页

InnoDB存储引擎工作时,需要以Page页为最小单位去将磁盘中的数据加载到内存中,与数据库相关的所有内容都存储在Page结构里。

Page分为几种类型,数据页索引页就是其中最为重要的两种类型。

3.1.3、修改缓冲区(插入缓冲区)insert buffer/change buffer

主要针对次要索引的数据插入存在的问题而设计。

我们都知道,在InnoDB引擎上进行插入操作时,一般需要按照主键顺序进行插入,这样才能获得较高的插入性能。当一张表中存在次要索引(除主键以外的索引)时,在插入时,数据页的存放还是按照主键进行顺序存放,但是对于次要索引叶节点的插入不再是顺序的了,这时就需要离散的访问次要索引页,由于随机读取的存在导致插入操作性能下降。

InnoDB为此设计了Change Buffer来进行插入优化。对于次要索引的插入或者更新操作,不是每一次都直接插入到索引页中,而是先判断插入的非主键索引是否在缓冲池中,若在,则直接插入;若不在,则先放入到一个Change Buffer中。看似数据库这个非主键的索引已经插到叶节点,而实际没有,这时存放在另外一个位置。然后再以一定的频率和情况进行Change Buffer和非聚簇索引页子节点的合并操作。这时通常能够将多个插入合并到一个操作中,这样就大大提高了对于非聚簇索引的插入性能。

3.1.4、自适应哈希索引

InnoDB会根据访问的频率和模式,为热点页建立哈希索引,来提高查询效率。

哈希(hash)是一种非常快的查找方法,在一般情况下这种查找的时间复杂度为O(1),即一般仅需要一次查找就能定位数据。而B+树的查找次数,取决于B+树的高度,在生产环境中,B+树的高度一般为34层,故需要34次的查询。

InnoDB存储引擎会监控对表上各索引页的查询。如果观察到建立哈希索引可以带来速度提升,则建立哈希索引,称之为自适应哈希索引(Adaptive Hash Index,AHI) AHI是通过缓冲池的B+树页构造而来,因此建立的速度很快,而且不需要对整张表构建哈希索引。 InnoDB存储引擎会自动根据访问的频率和模式来自动地为某些热点页建立哈希索引。

自适应哈希索引的建立有一个要求,即对这个页的连续访问模式必须是一样的。例如对于(a,b)这样的联合索引页,其访问模式可以是以下情况:

  • WHERE a=xxx
  • WHERE a= xxx and b=xxx

访问模式一样指的是査询的条件一样,若交替进行上述两种查询,那么 InnoDB存储引擎不会对该页构造AHI此外AHI还有如下的要求:

  • 以该模式访问了100次
  • 页通过该模式访问了N次,其中N>页中记录*1/16

根据 InnoDB存储引擎官方的文档显示,启用AHI后,读取和写入速度可以提高2倍,辅助索引的连接操作性能可以提高5倍。毫无疑问,AHI是非常好的优化模式,其设计思想是数据库自优化的(self-tuning),即无需DBA对数据库进行人为调整。

可以通过参数innodb_adaptive_hash_index来考虑禁用或启动此特性,默认是开启状态。

mysql> show variables like 'innodb_adaptive_hash_index';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| innodb_adaptive_hash_index | ON    |
+----------------------------+-------+
1 row in set (0.00 sec)

3.1.5、锁信息

我们都知道,InnoDB存储引擎会在行级别上对表数据进行上锁。不过InnoDB也会在数据库内部其他很多地方使用锁,从而允许对多种不同资源提供并发访问。数据库系统使用锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性。

3.1.6、数据字典信息

InnoDB有自己的表缓存,可以称为表定义缓存或者数据字典(Data Dictionary)。当InnoDB打开一张表,就增加一个对应的对象到数据字典。

数据字典是对数据库中的数据、库对象、表对象等的元信息的集合。在MySQL中,数据字典信息内容就包括表结构、数据库名或表名、字段的数据类型、视图、索引、表字段信息、存储过程、触发器等内容。MySQL INFORMATION_SCHEMA库提供了对数据局元数据、统计信息、以及有关MySQL server的访问信息(例如:数据库名或表名,字段的数据类型和访问权限等)。该库中保存的信息也可以称为MySQL的数据字典。

3.2、额外内存池(Addtional memory pool)

额外内存池是InnoDB存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间,控制参数 为:

innodb_additional_mem_pool_size

这个参数我们平时调整的可能不是太多,很多人都使用了默认值,可能很多人都不是太熟悉这个参数的作用。所以当我们一个MySQL Instance中的数据库对象非常多的时候,是需要适当调整该参数的大小以确保所有数据都能存放在内存中提高访问效率的。这个参数大小是否足够还是比较容易知道的,因为当过小的时候,MySQL 会记录 Warning 信息到数据库的 error log 中,这时候你就知道该调整这个参数大小了。

注:此参数在 MySQL 5.7.4 中移除。

3.3、Redo log Buffer重做日志缓冲区

redolog是一个顺序写的日志文件,符合WAL(Write Ahead Log)模式。顺序写比随机写效率高很多。redolog暂存已经提交成功的数据。若系统崩溃,可以使用redo log 恢复数据。

redolog 是一个缓冲区,定时将缓冲区的数据保存到磁盘。默认1秒保存一次。默认在执行commit操作之前刷新redo log buffer。

如上图所示,InnoDB在缓冲池中变更数据时,会首先将相关变更写入重做日志缓冲中,然后再按时或者当事务提交时写入磁盘,这符合Force-log-at-commit原则;

当重做日志写入磁盘后,缓冲池中的变更数据才会依据checkpoint机制择时写入到磁盘中,这符 合WAL原则。

在checkpoint择时机制中,就有重做日志文件写满的判断,所以,如前文所述,如果重做日志文件太小,经常被写满,就会频繁导致checkpoint将更改的数据写入磁盘,导致性能抖动。

操作系统的文件系统是带有缓存的,当InnoDB向磁盘写入数据时,有可能只是写入到了文件系统的缓存中,没有真正的“落袋为安”。

InnoDB的innodb_flush_log_at_trx_commit属性可以控制每次事务提交时InnoDB的行为。

mysql> show global variables like 'innodb_flush_log_at_trx_commit';
+--------------------------------+-------+
| Variable_name                  | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1     |
+--------------------------------+-------+
1 row in set (0.01 sec)
  • 当属性值为0时,事务提交时,不会对重做日志进行写入操作,而是等待主线程按时写入每秒写入一次;
  • 当属性值为1时,事务提交时,会将重做日志写入文件系统缓存,并且调用文件系统的fsync(操作系统的一个函数,调用此函数,操作系统会立即执行刷盘操作,将缓冲区的数据刷新到磁盘),将文件系统缓冲中的数据真正写入磁盘存储,确保不会出现数据丢失;
  • 当属性值为2时,事务提交时,也会将日志文件写入文件系统缓存,但是不会调用fsync,而是让文件系统自己去判断何时将缓存写入磁盘

innodb_flush_log_at_commit是InnoDB性能调优的一个基础参数,涉及InnoDB的写入效率和数据安全。当参数值为0时,写入效率最高,但是数据安全最低;参数值为1时,写入效率最低,但是数据安全最高;参数值为2时,二者都是中等水平。一般建议将该属性值设置为1,以获得较高的数据安全性,而且也只有设置为1,才能保证事务的持久性

日志的刷盘机制如下图所示:

3.4、内存数据落盘

3.4.1、整体思路分析

InnoDB内存缓冲池中的数据page要完成持久化的话,是通过两个流程来完成的,一个是脏页落盘;一个是预写redo log日志

当缓冲池中的页的版本比磁盘要新时,数据库需要将新版本的页从缓冲池刷新到磁盘。但是如果每次一个页发送变化,就进行刷新,那么性能开发是非常大的,于是InnoDB采用了Write Ahead Log(WAL)策略和Force Log at Commit机制实现事务级别下数据的持久性。

WAL要求数据的变更写入到磁盘前,首先必须将内存中的日志写入到磁盘;

Force-log-at-commit要求当一个事务提交时,所有产生的日志都必须刷新到磁盘上,如果日志刷新成功后,缓冲池中的数据刷新到磁盘前数据库发生了宕机,那么重启时,数据库可以从日志中 恢复数据。

为了确保每次日志都写入到重做日志文件,在每次将重做日志缓冲写入重做日志后,必须调用一次fsync操作,将缓冲文件从文件系统缓存中真正写入磁盘。

可以通过 innodb_flush_log_at_trx_commit 来控制重做日志刷新到磁盘的策略。

3.4.2、脏页落盘:

在数据库中进行读取操作,将从磁盘中读到的页放在缓冲池中,下次再读相同的页时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁 盘上的页。

对于数据库中页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称为CheckPoint的机制刷新回磁盘。

3.4.3、重做日志落盘:

那Log Buffer什么时候写入到redo log?

Log Buffer写入磁盘的时机,由参数 innodb_flush_log_at_trx_commit 控制,默认是 1,表示事务提交后立即落盘。

show VARIABLES like 'innodb_flush_log_at_trx_commit';

用户程序写入数据到磁盘文件时,需要调用操作系统的接口,操作系统本身是有缓冲区的,之后 依赖操作系统机制不时的将缓存中刷新到磁盘文件中。用户程序可以执行fsync操作将操作系统缓 冲区的数据刷入到磁盘文件中。

  • 0:MySQL每秒一次将数据从log buffer写入日志文件并同时fsync刷新到磁盘中。每次事务提交时,不会立即把 log buffer 里的数据写入到redo log日志文件的。如果MySQL崩溃或者服务器宕机,此时内存里的数据会全部丢失,最多会丢失1秒的事务。
  • 1:每次事务提交时,MySQL将数据将从log buffer写入日志文件并同时fsync刷新到磁盘中。该模式为系统默认,MySQL崩溃已经提交的事务不会丢失,要完全符合ACID,必须使用默认设置1。
  • 2:每次事务提交时,MySQL将数据从log buffer写入日志文件,MySQL每秒执行一次fsync操作将数据同步到磁盘中。每次事务提交时,都会将数据刷新到操作系统缓冲区,可以认为已经持久化磁盘,如果MySQL崩溃已经提交的事务不会丢失。但是如果服务器宕机或者意外断电,操作系统缓存内的数据会丢失,所以最多丢失1秒的事务。

只有设置为1是最安全但是性能消耗的方式,可以真正地保证事务的持久性,但是由于MySQL执行刷新操作 fsync() 是阻塞的,直到完成后才会返回,我们知道写磁盘的速度是很慢的,因此 MySQL 的性能会明显地下降。 0和2的性能最好的模式,综合安全性和性能的考虑,在业务中经常使用的2这种模式,在 MySQL异常重启时不会丢失数据,只有在服务器意外宕机时才会丢失1秒的数据,这种情况几率 是很低的,相对于性能来说,这时可以容忍的。

3.5、CheckPoint检查点机制

3.5.1、简介

如果重做日志可以无限地增大,同时缓冲池也足够大,那么是不需要将缓冲池中页的新版本刷新回磁盘。因为当发生宕机时,完全可以通过重做日志来恢复整个数据库系统中的数据到宕机发生的时刻。

但是这需要两个前提条件:

  1. 缓冲池可以缓存数据库中所有的数据;
  2. 重做日志可以无限增大

因此Checkpoint(检查点)技术就诞生了,目的是解决以下几个问题:

  1. 缩短数据库的恢复时间;
  2. 缓冲池不够用时,将脏页刷新到磁盘;
  3. 重做日志不可用时,刷新脏页。
  • 当数据库发生宕机时,数据库不需要重做所有的日志,因为Checkpoint之前的页都已经刷新回磁盘。数据库只需对Checkpoint后的重做日志进行恢复,这样就大大缩短了恢复的时间。
  • 当缓冲池不够用时,根据LRU算法会溢出最近最少使用的页,若此页为脏页,那么需要强制执行Checkpoint,将脏页也就是页的新版本刷回磁盘。
  • 当重做日志出现不可用时,因为当前事务数据库系统对重做日志的设计都是循环使用的,并不是让其无限增大的。重做日志可以被重用的部分是指这些重做日志已经不再需要,当数据库发生宕机时,数据库恢复操作不需要这部分的重做日志,因此这部分就可以被覆盖重用。如果重做日志还需要使用,那么必须强制Checkpoint,将缓冲池中的页至少刷新到当前重做日志的位置。

对于InnoDB存储引擎而言,是通过LSN(Log Sequence Number)来标记版本的。

LSN是8字节的数字,每个页有LSN,重做日志中也有LSN,Checkpoint也有LSN。可以通过命令 SHOW ENGINE INNODB STATUS 来观察:

mysql> show engine innodb status;
---
LOG
---
Log sequence number 2801228
Log flushed up to   2801228
Pages flushed up to 2801228
Last checkpoint at  2801219
0 pending log flushes, 0 pending chkp writes
117 log i/o's done, 0.00 log i/o's/second

Checkpoint发生的时间、条件及脏页的选择等都非常复杂。而Checkpoint所做的事情无外乎是将缓冲池中的脏页刷回到磁盘,不同之处在于每次刷新多少页到磁盘,每次从哪里取脏页,以及什么时间触发Checkpoint。

3.5.2、Checkpoint分类

在InnoDB存储引擎内部,有两种Checkpoint,分别为:Sharp Checkpoint、Fuzzy Checkpoint

sharp checkpoint:在关闭数据库的时候,将buffer pool中的脏页全部刷新到磁盘中。
fuzzy checkpoint:数据库正常运行时,在不同的时机,将部分脏页写入磁盘。仅刷新部分脏页到磁盘,也是为了避免一次刷新全部的脏页造成的性能问题。

Fuzzy Checkpoint 有四种:

  1. Master Thread Checkpoint; 定期落盘
  2. FLUSH_LRU_LIST Checkpoint;
  3. Async/Sync Flush Checkpoint;
  4. Dirty Page too much Checkpoint

1、Master Thread Checkpoint

在Master Thread中,会以每秒或者每10秒一次的频率,将部分脏页从内存中刷新到磁盘,这个过程是异步的。正常的用户线程对数据的操作不会被阻塞。

2、FLUSH_LRU_LIST Checkpoint

FLUSH_LRU_LIST checkpoint是在单独的page cleaner线程中执行的。

MySQL对缓存的管理是通过buffer pool中的LRU列表实现的,LRU 空闲列表中要保留一定数量的空闲页面,来保证buffer pool中有足够的空闲页面来相应外界对数据库的请求。

当这个空间页面数量不足的时候,发生FLUSH_LRU_LIST checkpoint

空闲页的数量由innodb_lru_scan_depth参数表来控制的,因此在空闲列表页面数量少于配置的 值的时候,会发生checkpoint,剔除部分LRU列表尾端的页面。

mysql> show variables like 'innodb_lru_scan_depth';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_lru_scan_depth | 1024  |
+-----------------------+-------+
1 row in set (0.00 sec)

因为InnoDB存储引擎需要保证LRU列表中需要有差不多100个空闲页可供使用。

在InnoDB1.1.x版本之前,需要检查LRU列表中是否有足够的可用空间操作发生在用户查询线程中,显 然这会阻塞用户的查询操作。倘若没有100个可用空闲页,那么InnoDB存储引擎会将LRU列表尾端的页移 除。如果这些页中有脏页,那么需要进行Checkpoint,而这些页是来自LRU列表的,因此称为 FLUSH_LRU_LIST Checkpoint。

而从MySQL 5.6版本,也就是InnoDB1.2.x版本开始,这个检查被放在了一个单独的Page Cleaner线 程中进行,并且用户可以通过参数innodb_lru_scan_depth控制LRU列表中可用页的数量,该值默认 为1024.

3、Async/Sync Flush Checkpoint

Async/Sync Flush checkpoint是在单独的page cleaner线程中执行的。

Async/Sync Flush checkpoint 发生在重做日志不可用的时候,将buffer pool中的一部分脏页刷新到磁盘中,在脏页写入磁盘之后,事务对应的重做日志也就可以释放了。

关于redo_log文件的的大小,可以通过 innodb_log_file_size 来配置。

mysql> show variables like 'innodb_log_file_size';
+----------------------+----------+
| Variable_name        | Value    |
+----------------------+----------+
| innodb_log_file_size | 50331648 |
+----------------------+----------+
1 row in set (0.00 sec)

对于是执行Async Flush checkpoint还是Sync Flush checkpoint,由 checkpoint_age 以及async_water_marksync_water_mark 来决定。

##即checkpoint_age等于最新的lsn减去已经刷新到磁盘的lsn的值 
checkpoint_age = redo_lsn-checkpoint_lsn 
async_water_mark = 75%*innodb_log_file_size 
sync_water_mark = 90%*innodb_log_file_size
  1. checkpoint_age<async_water_mark的时候,无需执行Flush checkpoint。也就说,redolog剩余空间超过25%的时候,无需执行Async/Sync Flush checkpoint。
  2. async_water_mark<checkpoint_age<sync_water_mark的时候,执行Async Flush checkpoint,也就说,redo log剩余空间不足25%,但是大于10%的时候,执行Async Flush checkpoint,刷新到满足条件1
  3. checkpoint_age>sync_water_mark的时候,执行sync Flush checkpoint。也就说,redo log剩余空间不足10%的时候,执行Sync Flush checkpoint,刷新到满足条件1。 在mysql 5.6之后,不管是Async Flush checkpoint还是Sync Flush checkpoint,都不会阻塞用户的查询进程。

总结:

由于磁盘是一种相对较慢的存储设备,内存与磁盘的交互是一个相对较慢的过程 由于innodb_log_file_size定义的是一个相对较大的值,正常情况下,由前面两种checkpoint刷新脏页到磁盘,在前面两种checkpoint刷新脏页到磁盘之后,脏页对应的redo log空间随即释放, 一般不会发生Async/Sync Flush checkpoint。同时也要意识到,为了避免频繁低发生Async/Sync Flush checkpoint,也应该将innodb_log_file_size配置的相对较大一些。 4、Dirty Page too much

Dirty Page too much Checkpoint是在Master Thread 线程中每秒一次的频率实现的。

Dirty Page too much 意味着buffer pool中的脏页过多,执行checkpoint脏页刷入磁盘,保证buffer pool中有足够的可用页面。

Dirty Page 由innodb_max_dirty_pages_pct配置,innodb_max_dirty_pages_pct的默认值在innodb 1.0之前是90%,之后是75%。

mysql> show variables like 'innodb_max_dirty_pages_pct';
+----------------------------+-----------+
| Variable_name              | Value     |
+----------------------------+-----------+
| innodb_max_dirty_pages_pct | 75.000000 |
+----------------------------+-----------+
1 row in set (0.00 sec)

3.6、Double Write双写落盘

如果说Insert Buffer给InnoDB存储引擎带来了性能上的提升,那么Double Write带给InnoDB存储引擎的是数据页的可靠性。

如上图所示,Double Write由两部分组成,一部分是内存中的double write buffer,大小为2MB,另一部分是物理磁盘上共享表空间连续的128个页,大小也为2MB。

在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是执行以下步骤:

  1. 通过memcpy函数将脏页先复制到内存中的double write buffer(大小为2M)区域
  2. 通过double write buffer再分两次,每次1MB顺序地写入共享表空间的物理磁盘上
  3. 然后马上调用fsync函数,把双写缓冲区的数据写入用户表空间中。

如果操作系统在将页写入磁盘的过程中发生了崩溃,在恢复过程中,InnoDB存储引擎可以从共享表空间中的double write中找到该页的一个副本,将其复制到表空间文件中,再应用重做日志。

场景1:系统表空间中的数据页写坏了,可以通过用户表空间的数据页+redolog恢复。

场景2:系统表空间中的数据页写完了,用户表空间写坏了。可以通过系统表空间的数据页恢复。

通过双写机制保证数据落盘过程万无一失。