InnoDB 磁盘 I/O 和 文件空间管理
InnoDB 磁盘 I/O
InnoDB 尽可能使用异步磁盘 I/O,通过创建多个线程来处理 I/O 操作,同时允许在 I/O 仍在进行时继续进行其他数据库操作。
-
在 Linux 和 Windows 平台上,InnoDB使用可用的操作系统和库函数来执行“本机”异步 I/O。
-
在其他平台上,InnoD B仍然使用 I/O 线程,但线程实际上可能会等待 I/O 请求完成,该技术称为“模拟” 异步 I/O。
预读(Read-Ahead)
如果 InnoDB 可以确定很快可能需要数据的可能性很高,它会执行预读(read-ahead)操作以将该数据放入缓冲池,以便它在内存中可用。对连续数据发出一些大型读取请求比发出几个小型分散请求更有效。
预读请求是异步预取缓冲池中的多个页面的 I/O 请求 ,以预测即将需要这些页面。这些请求将所有页面引入某一 范围内。 InnoDB使用两种预读算法来提高 I/O 性能:
-
线性预读(linear read-ahead),是一种根据缓冲池中按顺序访问的页面来预测可能很快需要哪些页面的技术。
如果 InnoDB 注意到表空间中某个段的访问模式是顺序的,它会提前将一批数据库页读取发送到 I/O 系统。
我们可以 InnoDB 使用配置参数 innodb_read_ahead_threshold 调整触发异步读取请求所需的顺序页面访问次数来控制何时执行预读操作。在添加该参数之前,InnoDB 只会在读取当前 extent 的最后一页时才计算是否对整个下一个 extent 发出异步预取请求。
innodb_read_ahead_threshold 可以控制检测顺序页面访问模式的敏感程度,默认值:56,即必须从一个扩展区顺序读取至少 56 个页面才能启动后续扩展区的异步读取,可选值范围:[0, 64],值为 0 表示禁用预读,值越高,访问模式检查越严格。
InnoDB 如果从一个盘区顺序读取的页数大于或等于 innodb_read_ahead_threshold,InnoDB 则启动整个后续盘区的异步预读操作。
-
随机预读(random read-ahead),是一种根据缓冲池中已有的页面来预测何时可能很快需要页面的技术,而不管这些页面的读取顺序如何。
如果 InnoDB 注意到表空间中的某些区域似乎正在被完全读入缓冲池,它将剩余的读取发送到 I/O 系统。
如果在缓冲池中找到来自同一盘区的 13 个连续页面, InnoDB 则异步发出请求以预取该盘区的剩余页面。要启用此功能,需要配置变量设置 innodb_random_read_ahead 为 ON。
双写缓冲区(Doublewrite Buffer)
InnoDB使用一种新颖的文件刷新技术,涉及一种称为双写缓冲区的结构 ,默认是启用的(innodb_doublewrite=ON)。它增加了意外退出或断电后恢复的安全性,并通过减少 fsync() 操作需求来提高大多数 Unix 系统的性能。
在将页面写入数据文件之前,InnoDB 首先将它们写入称为双写缓冲区的存储区域。只有在双写缓冲区的写入和刷新完成后,才会将 InnoDB 页面写入数据文件中的正确位置。如果在页面写入过程中,出现操作系统、存储子系统或者 mysqld 进程意外地退出,就会导致页面撕裂(torn page),InnoDB 可以在恢复期间从双写缓冲区中找到该页面的良好副本。
文件空间管理
我们可以使用 innodb_data_file_path 配置选项在配置文件中定义的数据文件形成 InnoDB 系统表空间。这些文件在逻辑上串联起来形成系统表空间。使用中没有条带化。您无法定义表在系统表空间内的分配位置。在新创建的系统表空间中,InnoDB 从第一个数据文件开始分配空间。
为了避免在系统表空间内存储所有表和索引所带来的问题,我们可以启用 innodb_file_per_table 配置选项,它将每个新创建的表存储在单独的表空间文件中(扩展名为 .ibd)。对于以这种方式存储的表,磁盘文件内的碎片较少,并且当表被截断时,空间将返回给操作系统,而不是仍然由 InnoDB 在系统表空间中保留。
我们还可以将表存储在通用表空间中,通用表空间是使用CREATE TABLESPACE 语法创建的共享表空间,它们可以在 MySQL 数据目录之外创建,能够保存多个表,并支持所有行格式的表。
页、范围、段和表空间
page
页面(page):InnoDB 在磁盘(data files)和内存(buffer pool)之间传输数据的基本单位。
一个页面可以包含一个或多个行,这具体取决于每行中有多少数据,如果一行不能完全放入一页,InnoDB 会设置额外的指针式数据结构,以便有关该行的信息可以存储在一页中。为了提升 I/O 吞吐量,InnoDB 的读取和写入操作,都是按照一个或多个页进行的,会一次性读取或写入一个范围。
在每页中容纳更多数据的一种方法是使用压缩行格式(compressed row format),对于使用 BLOB 或大型文本字段的表,紧凑行格式允许将这些大型列与行的其余部分分开存储,从而减少不引用这些列的查询的 I/O 开销和内存使用量。
每个表空间都是由数据库页面(page)组成的,MySQL 实例中的每个表空间都具有相同的页面大小。
通过选项
innodb_page_size
控制可以修改 page size,其默认值为:16384,即 所有表空间的页面大小均为 16KB,我们可以在创建 MySQL 实例时,通过 innodb_page_size 选项调整页面大小。
默认的 16KB 页面大小或更大的空间,适用于各种工作负载,特别是涉及全表扫描的查询和涉及批量更新的 DML 操作。
-
对于涉及许多小型写入的联机事务处理(on-line transaction processing,OLTP) 工作负载,较小的页面大小可能更有效;
OLTP 表示事务性非常高的系统,一般都是高可用的在线系统,以小的事务以及小的查询为主,评估其系统的时候,一般看其每秒执行的 Transaction 以及 Execute SQL 的数量。在这样的系统中,单个数据库每秒处理的 Transaction 往往超过几百个,或者是几千个,Select 语句的执行量每秒几千甚至几万个。典型的OLTP系统有电子商务系统、银行、证券等,
-
当单个页面包含许多行时,可能会面临竞争等问题;
-
对于 SSD 存储设备来说,较小的页面也可能会更高效,因为,SSD 存储设备通常使用较小的块大小。
保持 InnoDB 的页面大小接近存储设备的块大小,可以最大限度地减少重写到磁盘的未更改数据量。
Extent
表空间中的一组连续的页面会被称为扩展区(extent)。
对于16KB的页面,一个扩展区会包含64个页面,当页面 size 小于等于 16KB 时,扩展区 size 为 1MB。
page size | extent size | pages per extent |
---|---|---|
4KB | 1MB | 256 |
8KB | 1MB | 128 |
16KB | 1MB | 64 |
32KB | 2MB | 64 |
64KB | 4MB | 64 |
Segment
表空间内的“文件”在 InnoDB 中称为段(segments)。
这些段与回滚段不同,回滚段实际上包含许多表空间段。
当表空间内的段增长时, InnoDB 会一次性为它分配前 32 页;之后,InnoDB 才将整个扩展区分配给该段。InnoDB 一次最多可以将 4 个扩展区添加到一个大段中,以保证数据良好的顺序性。
InnoDB 中为每个索引分配两个段:B-tree 的 非叶子节点(nonleaf nodes) 和 B-tree 的叶子节点(leaf nodes)。保持叶子节点在磁盘上的连续性可以实现更好的顺序 I/O 操作,因为这些叶节点包含实际的表数据。
表空间中的某些页包含其他页的位图,因此 InnoDB 表空间中的一些扩展区不能作为整体分配给段,而只能作为单独的页。
通过 SHOW TABLE STATUS
语句可以查询表空间中的可用空间,InnoDB 会报告表空间中明确空闲的范围。InnoDB 总是保留一些范围用于清理和其他内部目的;这些保留区不包含在可用空间中。
当我们从表中删除数据时,InnoDB 会收缩相应的 B-tree 索引。释放的空间是否可供其他用户使用取决于删除模式是否将单个页或范围释放到表空间;删除表或删除其中的所有行可以保证将空间释放给其他用户。
注意,删除的行只能通过清除(purge)操作来物理删除,在事务回滚或一致性读(快照读)不再需要它们之后的一段时间,清除操作会自动发生。
配置保留文件段页面的百分比
变量 innodb_segment_reserve_factor 定义了表空间文件的段页中保留为空页的百分比。保留一定比例的页面以供将来增长,以便 B 树中的页面可以连续分配。修改保留页百分比的能力允许对 InnoDB 进行微调,以解决数据碎片或存储空间使用效率低下的问题。
innodb_segment_reserve_factor 的默认值为 12.5,即每个段中空页占 12.5%,可选值的范围:[0.03, 40]。该设置适用于每表文件和通用表空间。
页面如何与表行相关
最大行长度
最大行长度:
-
对于 4KB、8KB、16KB 和 32KB 的页面大小,最大行长度略小于数据库页大小的一半;
例如,对于默认的 16KB InnoDB 页面大小,最大行长度略小于 8KB。
-
对于 64KB 的页面大小,最大行长度略小于 16KB。
行存储策略
-
如果某行不超过最大行长度,则所有行都存储在本地页面内;
-
如果某行超过最大行长度,则会选择可变长度列进行外部页外存储,直到该行符合最大行长度限制。
可变长度列的外部页外存储因行格式而异:
-
紧凑和冗余行格式(COMPACT and REDUNDANT Row Formats)
当选择可变长度列用于外部页外存储时,InnoDB 将前 768 个字节存储在本地行中,其余部分存储在外部溢出页中,每个这样的列都有自己的溢出页面列表。768 字节前缀附带一个 20 字节值,该值存储列的真实长度并指向存储其余值的溢出列表。
-
动态和压缩行格式(DYNAMIC and COMPRESSED Row Formats)
当选择可变长度列用于外部页外存储时,InnoDB 将 20 字节指针本地存储在行中,其余部分在外部存储到溢出页中。
-
LONGBLOB 和 LONGTEXT 列必须小于 4GB,并且行的总长度(包括 BLOB 和 TEXT 列)必须小于 4GB。
InnoDB Checkpoints
将日志文件设置得非常大可能会减少检查点(checkpiont)期间的磁盘 I/O 。将日志文件的总大小设置为与缓冲池一样大甚至更大通常是有意义的。
检查点处理的工作原理
InnoDB 实现了称为模糊检查点(fuzzy checkpointing)的检查点机制,InnoDB 小批量地从缓冲池中刷新修改的数据库页面。无需在单个批次中刷新缓冲池,避免在检查点过程中中断用户 SQL 语句的处理。
在崩溃恢复期间, InnoDB 会查找写入日志文件的检查点标签,它知道标签之前对数据库的所有修改都存在于数据库的磁盘映像中。然后 InnoDB 从检查点向前扫描日志文件,将记录的修改应用到数据库。
参考: