MySQL 基本概念

在研究 InnoDB 存储引擎之前,有必要复习几个 MySQL 的基本概念。

主键

Primary Key

主键是指 一列一组列,其值能 唯一区分 表中的每一行数据,而且其值不能为空。

主键是一个 逻辑对象

MySQL 对主键会自动生成唯一索引,所以主键也是一个特殊的索引。

主键不一定是一个字段,可能是多个字段。

索引

Index

索引是帮助 MySQL 高效获取数据 的数据结构。它是一个存在于磁盘中的 物理结构,其中包含若干个 ,来自于表格中的一个或多个字段,这些键保存于 B-tree 的结构中,使得 “查找与键值相关的行” 能够快速、高效地进行。

索引是由表格或视图创建而来的,它与书本中的目录非常相似,可以大大缩短找到特定数据所需的时间。

索引用于快速查找包含特定字段值的行。如果没有索引,MySQL 必须从第一行开始逐行对整个表格进行彻底的检查,表格越大,开销越大。如果表格有一个这个字段的索引,就可以快速地判定这一行的位置,而无需查看所有的数据。这种方法要比逐行查看整行数据要快的多。

大部分 MySQL 索引是以 B-Tree 的形式保存的。

索引的种类

  • Clustered:聚集索引
  • Non-Clustered:非聚集索引
  • Unique:唯一索引
  • Filtered
  • XML
  • Full Text
  • Spatial:立体索引
  • Columnstore
  • Index with included columns
  • Index on computed columns

聚集索引 与 非聚集索引

Clustered Index / Non-Clustered Index

聚集索引

image-center

每个 InnoDB 表格都有一个特殊的索引,称为聚集索引。

每个表格只能拥有 一个聚集索引,因为表格中所有行只能按一个顺序存储。

一般情况下,聚集索引主键 是同义词。

聚集索引就是按照每张表的主键构建一个 B+树,叶子节点存放的即为整张表的行记录数据,聚集索引的叶子节点也称为 数据页。聚集索引的结构决定了 数据行也是索引的一部分。由于 B+树 索引本身是有序的,同时数据行也存储在叶子节点上,因此通过聚集索引能够实现快速查询。

  • 为表格定义主键时,InnoDB 会将其做为聚集索引使用。一定要给每个表格都定义一个主键,如果没有逻辑唯一且非空的字段,可以单独添加一个自增字段,字段值会自动填充。
  • 如果没有为表格指定主键,MySQL 会寻找第一个非空唯一索引,将其做为聚集索引。
  • 如果表格既没有主键,也没有合适的唯一索引,InnoDB 会生成一个隐藏的聚集索引,名为 GEN_CLUST_INDEX,其中包含每行的 ID。表格中的所有行是根据 InnoDB 所分配的行 ID 来 排序 的。行 ID 是一个 6 字节的字段,有行插入时会自动加一。

因此,无论如何,InnoDB 的表中一定会有一个聚集索引,要么是显示创建,要么是隐式创建的。

因为实际的数据页只能按照一颗 B+树 进行排序,因此,每个表格只能有一个聚集索引。

在多数情况下,查询优化器倾向于采用聚集索引,因为聚集索引能够在 B+树 叶子节点上直接找到数据。(聚集索引对主键的排序查找和范围查找速度非常快。因为叶子节点就维护了数据行。)

聚集索引如何提升查询速度

通过聚集索引来访问某一行是很快的,因为在索引中进行的搜索,最终会直接访问到行所在的页面。

如果表格特别大,聚集索引经常会节省很多 I/O 操作。

辅助索引

Secondary Index

聚集索引以外的其它索引都叫辅助索引。

image-center

对于辅助索引,叶子节点并不包含行记录的全部数据。叶子节点除了包含键值以外,每个叶子节点的索引行中还包含一个 主键值。通过主键值,InnoDB 存储引擎可以再次在聚集索引中找到对应索引项的行数据。

辅助索引的存在不会影响数据在聚集索引中的组织,因此每张表上可以存在多个辅助索引。

当通过辅助索引来查找数据时,InnoDB 存储引擎会遍历辅助索引,并获得页中的指向主键索引的主键,然后再通过主键索引依次找到一个完整的行记录。因此,这就意味着通过辅助索引查找行,存储引擎先找到辅助索引的叶子节点,得到对应的主键值,然后根据该主键值去聚集索引上查找对应的行数据。也即一次辅助索引查找需要两次对 B+树 进行查找。

主键越长,辅助索引就会占用更多的空间,因此主键越短越好。

非聚集索引

Non-Clustered Index

非聚集索引其结构是与数据行分离的,每个非聚集索引包含非聚集索引的键值,每个键值条目都有一个指针,指向包含该键值的数据行。在聚集索引中,最终端的叶子节点就是表格数据本身,而非聚集索引则不然,其最终端的叶子节点只是一些指针而已,指向数据的存储位置。非聚集索引是与表格数据分开保存的,因此,一个表格可以拥有多个非聚集索引。

在非聚集索引中,数据是按索引字段的正序或逆序排列的,其排列顺序不会影响表格数据的存储。

二者的区别
  • 每个表格只能有一个聚集索引,可以有多个非聚集索引
  • 聚集索引查找数据更快。因为如果要找的字段不在索引中,非聚集索引还要回到表格去查找。

唯一索引

Unique Index

唯一索引用于保证索引中键值的唯一性。在增加一个唯一约束时,会在后台自动创建一个唯一索引。

唯一索引可以用一个字段,也可以是组合索引,而且可以为空。

如果能确定某个字段将只包含彼此不同的值,为这个字段创建索引的时候,就应该用关键字 UNIQUE 把它定义为一个唯一索引。

这么做的好处:

  • 简化了 MySQL 对这个索引的管理工作,这个索引也因此而变得更有效率;
  • MySQL 会在有新记录插入数据表时,自动检查新记录的这个字段的值是否已经在某个记录的这个字段里出现过了;如果是,MySQL将拒绝插入新记录。

也就是说,唯一索引可以 保证数据记录的唯一性。事实上,在许多场合,人们创建唯一索引的目的往往不是为了提高访问速度,而只是为了避免数据出现重复。

何时使用唯一约束而不用唯一索引:数据的 完整性 更加重要时

主键索引是唯一索引的特殊类型。

默认情况下,一个 主键约束 会创建一个唯一的 聚集索引,而一个 唯一性约束 会创建一个唯一的 非聚集索引。这些默认设置可以自行修改。

约束

Constraint

MySQL 中,约束是指对表格中的字段如何取值定义一些规则。

按照这个规则,允许或限制特定的值保存到该字段中。

  • 引入约束的目的在于保证数据库的完整性
  • 约束用来限制向表格中插入数据的类型

约束分为两类:字段级 和 表格级。字段级约束保能应用于一个字段,表格级应用于整个表格。

约束的种类

NOT NULL

非空约束。

使用该约束以后,该字段的值不能为空,即 NULL,必须是具体的数据。

该约束可以在创建、修改表格时使用。

mysql> create table t_user(
    -> id int(10),
    -> name varchar(32) not null
    -> );

向表格插入记录时,如果该字段没赋值,会报错。

UNIQUE

唯一性约束。

使用该约束以后,MySQL 会禁止向该字段插入重复的值。

PRIMARY KEY

主键约束。

一张表只能有一个主键约束。

使用该约束以后,指定的字段将被作为该表格的主键,因此不允许为空,也不允许重复。

  • 单一主键:给一个字段添加主键约束
  • 复合主键:给多个字段联合添加一个主键约束(只能用表级约束)
FOREIGN KEY

外键约束。

MySQL 会通过两个表格中都存在的某个字段,在两个表格之间创建一个链接。该字段在一个表格中必须是主键,相对的,在另一个表格中,该字段就称为外键。

一张表可以有多个外键字段。

  • 单一外键:给一个字段添加外键约束
  • 复合外键:给多个字段联合添加一个外键约束
CHECK

检查约束。

启用该约束时,可以用逻辑表达式来限制该字段的值的有效模板。

但 MySQL 直到 8.0 版本仍未支持该功能,虽然它能够解析该子句,却会忽略掉。

The CHECK clause is parsed but ignored by all storage engines.

在需要使用检查约束的场景下,通常会使用触发器来实现。

USE office
CREATE TRIGGER staff-check
	BEFORE INSERT ON Staff
	FOR EACH ROW
BEGIN
	IF NEW.id<0 THEN
	NEW.id=0
	END IF;
END
DEFAULT

默认约束。为字段指定默认值。

使用该约束以后,该字段必须包含有效的值,包括 NULL。

向表格插入数据时,如果没有为该字段提供有效值,该字段就会使用默认约束所指定的值。

InnoDB 的优点

InnoDB 是一个通用的存储引擎,它在高可用与高性能之间有着比较好的平衡。在 MySQL 8.0 中,它是默认的存储引擎。

  • 其 DML 操作遵循 ACID 模型,具有 事务性 的提交、回滚和故障恢复功能,便于保护用户数据。
  • 精细到行的锁定,Oracle 风格的一致读取,提升了多用户 并发数性能
  • InnoDB 的表格可以 管理磁盘中的数据,以优化基于主键的查询。每个表格都有一个主键索引,称为集群索引,用它来组织数据,以减小用于寻找主键的 I/O。
  • 为了保持数据的一致性,InnoDB 支持 FOREIGN KEY 约束,即 外键约束。使用外键以后,会检查插入、更新和删除的操作, 以确保它们不会导致不同表之间的不一致。
  • 它有 崩溃恢复功能 :如果服务器由于硬件或软件故障而崩溃,不用考虑当时数据库当时发生了什么,重启数据库后不需要做任何特殊的操作。InnoDB 的会自动完成崩溃之前所提交的修改,回退尚未提交却当时正在进行的修改。
  • InnoDB 存储引擎会维护其自己的 缓冲池,用于访问数据时,在内存中缓存表格和索引数据。经常使用的数据可以直接在内存中处理。该缓存可以用于多种类型的信息。在专用的数据库服务器上,通常超过 80% 的物理内存都分配给了缓冲池。
  • 外键 保证数据完整性:把相关的信息拆分到不同的表格中以后,可以通过设置外键来实现引用的完整性。更新或删除数据时,其它表格中相关的数据也会随之自动更新或删除。如果试图在第二个表中插入数据,但在第一个表格中没有对应的数据,插入会失败。
  • 如果磁盘或内存中的数据被 损坏,校验机制会在用户使用数据之前发出 警告
  • 为数据库中的每个表格定义主键时,针对主键的操作会被自动优化。在 WHEREORDER BYGROUP BY 等子句中,以及在使用连接操作符时,可以非常快速地引用主键字段。
  • 插入、更新、删除会被 自动优化,该机制称为 缓冲修改,即 change buffering。InnoDB 不仅允许 同时读、写 同一个表格,还会把发生修改的数据缓存到流水线上的磁盘 I/O 中。
  • 不仅是对巨大表格进行长时间的查询得到了性能的提升,当相同的行被反复访问时,会使用 自适应哈希索引 来优化,使查询变得更加快速,如同它们是从哈希表格里出来的。
  • 可以 压缩 表格及相关索引。
  • 创建或删除索引时,对性能与可用性的影响更小。
  • 清空 file-per-table 表空间的速度非常快,并且可以 直接释放磁盘空间,而不只是系统表空间。
  • 表格数据的存储布局对于 BLOB 类型和 LONGTEXT 类型的字段更加高效,使用 DYNAMIC 行格式。
  • 可以通过查询 INFORMATION_SCHEMA 表格来监测存储引擎内部的工作情况。
  • 可以通过查询 Performance Schema 表格来监测存储引擎性能细节。
  • 可以随意地把 InnoDB 的表格 与其它 MySQL 存储引擎的表格组合 到一起,甚至可以用在同一个语句中。例如,可以仅在一个查询中,使用一个连接操作符把 InnoDB 和 MEMORY 表格合并到一起。
  • 处理较大的数据时,性能更高,CPU 占用更少。
  • 可以应对数量庞大的数据。

DML:Data Manipulation Language。数据操作语言,是一组 SQL 语句,用于进行 INSERTUPDATEDELETE 等操作。

ACID:Atomicity, Consistency, Isolation, Durability。原子性,一致性,独立性,耐用性。

BLOB:Binary Large Object,二进制大对象。是数据库中用来存储二进制文件的字段类型,是一个可以存储二进制文件的容器。BLOB 是一个大文件,典型的是图片、音频、视频文件,由于它们的尺寸较大,必须使用特殊的方式来处理。

InnoDB 的架构

缓冲池

Buffer Pool

缓冲池是内存中的一块,当有数据被访问时,InnoDB 会把表格和索引数据缓存到这里。借助缓冲池可以直接在内存中处理这些频繁使用的数据,大大提升了处理速度。在专用的数据库服务器上,通常超过 80% 的物理内存都分配给了缓冲池。

为了提高大容量读取操作的效率,缓冲池被分为多个 页面,每页面可容纳多

为了高效管理缓存,缓冲池做成一个互相链接的页面列表。很少用到的数据会从缓存中过期。

变更缓冲区

Change Buffer / Insert Buffer

变更缓冲区的主要目的是将 “对辅助索引的数据操作” 缓存下来,以此减少辅助索引的随机 I/O,并达到操作合并的效果。

变更缓冲区是一个特殊的数据结构,如果受影响的页面不在缓冲池中,对辅助索引页面的修改 就会被缓存到变更缓冲区中。

这些被缓冲的变更可能来自 INSERTUPDATEDELETE 等操作,当受影响的页面被加载到缓冲池时,变更缓冲区中的这些修改就会被合并。

在 MySQL 5.5 之前的版本中,由于只支持缓存 insert 操作,所以最初叫做 Insert Buffer,只是后来的版本中支持了更多的操作类型缓存,才改叫 Change Buffer。因此,代码中经常会看到 ibuf 前缀开头的函数或变量。

自适应哈希索引

Adaptive Hash Index

哈希(hash)是一种非常快的查找方法,一般情况下查找的时间复杂度为O(1)。常用于连接(join)操作,如 SQL Server 和 Oracle 中的哈希连接(hash join)。但是 SQL Server 和 Oracle 等常见的数据库并不支持哈希索引(hash index)。MySQL 的 Heap 存储引擎默认的索引类型为哈希,而 InnoDB 存储引擎提出了另一种实现方法,自适应哈希索引。

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

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

值得注意的是,哈希索引只能用来搜索等值的查询,如 select * from table where index_col = 'xxx',而对于其他查找类型,如范围查找,是不能使用的。

可以通过参数 innodb_adaptive_hash_index 来禁用或启动此特性,默认为开启。

重做日志缓冲区

Redo Log Buffer

它是内存上的一块,其中保存的数据是准备写入重做日志的。该缓冲区会周期性地刷新到磁盘上的日志文件中。如果该缓冲区足够大,可以运行较大的事务,最重要的是在提交事务之前,无需再等待把重做日志写入磁盘了。因此,如果要对大量的行进行更新、插入或删除等事务,建议把该缓冲区加大,以节省磁盘 I/O。

表空间

系统表空间

系统表空间中包含:

数据字典,双写缓冲区,变更缓冲区,回滚日志。

系统表空间是共享表空间,由多个表格共享。使用一个或多个数据文件,即 ibdata1 等。

独立表空间

启用 innodb_file_per_table 选项之后,会为每个数据库建立单独的表空间文件。

一般表空间

一般表空间可以在数据目录之外创建,可以包含多个表格。

回滚表空间

回滚表空间包含一个或多个文件,内容为回滚日志。

临时表空间

临时表

顾名思义,就是临时的,用完销毁掉的表。 数据既可以保存在临时的文件系统上,也可以保存在固定的磁盘文件系统上。

  • 全局临时表:从数据库实例启动后开始生效,在数据库实例销毁后失效。这种临时表对应的是内存表,即 memory 引擎。
  • 会话级临时表:在用户登录系统成功后生效,在用户退出时失效。是用 create temporary table 创建的表。
  • 事务级临时表:在事务开始时生效,事务提交或者回滚后失效。MySQL 中没有这种临时表,必须利用会话级的临时表间接实现。
  • 检索级临时表:在 SQL 语句执行之间产生,执行完毕后失效。 MySQL 中,这种临时表随默认存储引擎变化。默认存储引擎是 MyISAM 时,临时表的引擎就是 MyISAM,并且文件生成形式以及数据运作形式和 MyISAM 一样,只是数据保存在内存里;如果默认引擎是 INNODB,临时表的引擎就是 INNODB,此时它的所有信息都保存在共享表空间里面。

双写缓冲区

双写缓冲区

InnoDB 使用了一种叫做 doublewrite 的特殊文件 flush 技术,在把页面写到数据文件之前,InnoDB 先把它们写到双写缓冲区中,它是一个连续的区域。写完后,InnoDB 才会把页面写到数据文件的适当的位置。如果在写页面的过程中发生意外崩溃,InnoDB 在稍后的恢复过程中,会从双写缓冲区中找到完好的页面副本用来恢复。

部分页面写入问题

InnoDB 的页面大小一般是 16KB,其数据校验也是针对这 16KB 来计算的,将数据写入到磁盘是以页面为单位进行操作的。而操作系统在写文件时,通常是以 4KB 作为单位的,即块大小为 4KB,那么每写一个 InnoDB 的页面到磁盘上,操作系统需要写 4 个块。而计算机硬件和操作系统,在极端情况下(比如断电)往往并不能保证这一操作的原子性,16K 的数据,写入 4K 时,发生了系统断电或系统崩溃,只有一部分写是成功的,这种情况下就是 partial 页面 write(部分页写入)问题。此时页面数据会出现不一致,从而形成一个 “断裂” 的页面,使数据产生混乱。InnoDB 对这种块错误是无能为力的。

有人会认为系统恢复后,MySQL 可以根据 重做日志 进行恢复,而 MySQL 在恢复的过程中是检查页面的校验和,校验和就是页面的最后事务 ID,发生部分页面写入问题时,页面已经损坏,找不到该页面中的事务 ID,就无法恢复。

双写缓冲区的由来

双写缓冲区是 InnoDB 在表空间上的 128 个页(2个节),大小为 2MB。为了解决部分页面写入问题,当 MySQL 将脏数据刷新到数据文件的时候, 先使用 memcopy 将脏数据复制到内存中的双写缓冲区,之后通过双写缓冲区再分两次写入,每次写入 1MB 到共享表空间,然后马上调用 fsync 函数,同步到磁盘上,避免缓冲带来的问题。

在此过程中,“双写” 是顺序写,开销并不大,在完成双写后,再将双写缓冲区写入各表空间文件,这时是离散写入。

所以在正常的情况下, MySQL 写数据页面时,会写两遍到磁盘上,第一遍是写到双写缓冲区,第二遍是从双写缓冲区写到真正的数据文件中。如果发生了极端情况(断电),InnoDB 再次启动后,发现了一个页面数据已经损坏,那么此时就可以从双写缓冲区中进行数据恢复了。

缺点

位于共享表空间上的双写缓冲区实际上也是一个文件,写共享表空间会导致系统有更多的 fsync 操作, 而硬盘的 fsync 性能因素会降低 MySQL 的整体性能,但是并不会降低到原来的 50%。这主要是因为:

双写是在一个连续的存储空间,所以硬盘在写数据的时候是顺序写,而不是随机写,这样性能更高。 将数据从双写缓冲区写到真正的段中的时候,系统会自动合并连接空间刷新的方式,每次可以刷新多个页面。

使用场景

在一些情况下可以关闭双写以获取更高的性能。比如在从服务器上,因为即使出现了部分页面写入问题,数据还是可以从中继日志中恢复的。设置 InnoDB_doublewrite=0 即可关闭双写缓冲区。

回滚日志

Undo Log

回滚日志用于存放数据被修改之前的值,如果该修改出现异常,可以使用回滚日志实现回滚操作,保证事务的一致性。回滚日志是 InnoDB MVCC 事务特性的重要组成部分。

当我们对记录做了变更操作时就会产生回滚记录,回滚记录默认被记录到系统表空间中,但从 5.6 开始,也可以使用独立表空间。它保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),即非锁定读。

回滚记录中存储的是老版本数据,当一个旧的事务需要读取数据时,为了能读取到老版本的数据,需要顺着回滚链找到满足其可见性的记录。当版本链很长时,通常可以认为这是个比较耗时的操作。

对数据的变更操作通常为 INSERTDELETEUPDATE,其中 INSERT 操作在事务提交前只对当前事务可见,因此产生的回滚日志可以在事务提交后直接删除(谁会对刚插入的数据有可见性需求呢!!),而对于 UPDATEDELETE 则需要维护多版本信息。在 InnoDB 里,UPDATEDELETE 操作产生的回滚日志被归成一类,即 update_undo

原理

为了满足事务的原子性,在操作任何数据之前,首先将数据备份到回滚日志,然后进行数据的修改。如果出现了错误或者用户执行了 ROLLBACK 语句,系统可以利用回滚日志中的备份,将数据恢复到事务开始之前的状态。

除了可以保证事务的原子性,回滚日志也可以用来辅助完成事务的持久化。

回滚日志是为了实现事务的原子性,在 MySQL 数据库 InnoDB 存储引擎中,还用回滚日志来实现 MVCC。

流程范例

假设有 A、B 两个数据,值分别为 1、2。

  1. 事务开始
  2. 把 A=1 记录到回滚日志
  3. 修改 A=3
  4. 把 B=2 记录到回滚日志
  5. 修改 B=4
  6. 将回滚日志写入磁盘
  7. 将数据写入磁盘
  8. 提交事务

这里有一个隐含的前提条件:“ 数据都是先读到内存中,然后修改内存中的数据,最后将数据写回磁盘 ”。

之所以能同时保证原子性和持久化,是因为以下特点:

  • 更新数据前,记录回滚日志。
  • 为了保证持久性,必须将数据在事务提交前写到磁盘。只要事务成功提交,数据必然已经持久化。
  • 回滚日志必须先于数据持久化到磁盘。如果在 7-8 之间系统崩溃,回滚日志是完整的,可以用来回滚事务。
  • 如果在 1-6 之间系统崩溃,因为数据没有持久化到磁盘。所以磁盘上的数据还是保持在事务开始前的状态。

缺陷

每个事务提交前将数据和回滚日志写入磁盘,这样会导致大量的磁盘 I/O,因此性能很低。如果能够将数据缓存一段时间,就能减少 I/O,提高性能。但是这样就会丧失事务的持久性。

回滚日志分类

  • INSERT_UNDO:插入操作,记录插入的唯一键值
  • UPDATE_UNDO:更新与删除操作,记录修改的唯一键值以及原字段记录。

日志内容

逻辑格式的日志,在执行回滚时,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上操作实现的,这一点是不同于重做日志的。

写入时机

事务开始之前,将当前的版本生成回滚日志,回滚也会产生重做,以保证回滚日志的可靠性。

日志空间的释放

当事务提交之后,回滚日志并不会立即删除,而是放入待清理的链表,由 purge 线程判断。是否可以清理回滚日志空间,决定于是否有其他事务在使用 “回滚段中表的前一个事务之前的” 版本信息。

重做日志

Redo Log

重做日志是基于磁盘的数据结构,用于崩溃后的 自动恢复

在 mysqld 启动时,InnoDB 会进行自动恢复,以纠正由不完整的事务写入的数据。在 mysqld 意外关闭前一刻没有完成更新数据文件的事务,会在 mysqld 启动时自动重演。重演时会使用日志序列编号,即 Log Sequence Number,LSN。

如果对数据的修改量很大,会无法快速写入磁盘,因此,它们会先被保存到 REDO 日志中,然后再保存到磁盘上。

在重做日志中,所有发生的变更会被详细记录:行 ID、原字段值、新字段值,会话 ID、时间。

一旦提交成功,数据就安全地保存到磁盘的数据文件中了。

Innodb 的事务日志就是指重做日志,保存在日志文件 ib_logfile* 中。

参数

重做日志可以通过参数 innodb_log_files_in_group 配置成多个文件,另外一个参数 innodb_log_file_size 表示每个文件的大小。因此总的重做日志大小为 innodb_log_files_in_group * innodb_log_file_size

重做日志文件以 ib_logfile[number] 命名,日志目录可以通过参数 innodb_log_group_home_dir 控制。

日志的写入

重做日志以顺序的方式写入日志文件,写满时则回溯到第一个文件,进行覆盖写。(但在做 redo checkpoint 时,也会更新第一个日志文件的头部 checkpoint 标记,所以严格来讲也不算顺序写)

重做日志文件是循环写入的,在覆盖写之前,总是要保证对应的脏页已经刷到了磁盘。在非常大的负载下,重做日志可能产生的速度非常快,导致频繁的刷脏操作,进而导致性能下降,通常在未做 checkpoint 的日志超过文件总大小的 76% 之后,InnoDB 认为这可能是个不安全的点,会强制 preflush 脏页,导致大量用户线程卡住(stalled)。如果可预期会有这样的场景,建议加大重做日志文件的大小。可以做一次干净的 shutdown,然后修改重做日志配置,重启实例。

原理

和回滚日志相反,重做日志记录的是新数据的备份。在事务提交前,只要将重做日志持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是重做日志已经持久化。系统可以根据重做日志的内容,将所有数据恢复到最新的状态。

Undo + Redo 事务的简化过程

假设有 A、B 两个数据,值分别为 1、2。

  1. 事务开始
  2. 把 A=1 记录到回滚日志
  3. 修改 A=3
  4. 把 A=3 记录到重做日志
  5. 把 B=2 到回滚日志
  6. 修改 B=4
  7. 把 B=4 记录到重做日志
  8. 将重做日志写入磁盘
  9. 提交事务

Undo + Redo事务的特点

  • 为了保证持久性,必须在事务提交前将重做日志持久化。
  • 数据不需要在事务提交前写入磁盘,而是缓存在内存中。
  • 重做日志保证事务的持久性。
  • 回滚日志保证事务的原子性。
  • 有一个隐含的特点,数据必须要晚于重做日志写入持久存储。