MySQL InnoDB Architecture 简要介绍

2023-06-25,,

MySQL InnoDB 存储引擎整体架构图:

一、内存存储结构

1、Buffer Pool

buffer pool 是主内存中的一块儿存储区域,用于存储访问的表及索引数据。这样从内存中直接访问获取使用的数据可以极大的提升访问效率。在一些特殊专用的服务里,几乎 80% 的内存区域都被赋于 buffer pool。

为了提升大数据量读操作效率,buffer pool 被设计划分为能够存储多条记录的数据页。同时,基于链表结构存储实现,LRU算法支持,能够极大的提高缓存管理的效率。

Buffer Pool LRU 算法

buffer pool 使用基于LRU算法的列表数据结构实现。当需要添加新的数据页,最近最少使用的数据页会淘汰,新的数据页会被插入到列表的中间。

中间插入策略会把列表当成两个子列表:

头部用于存储新的最新访问的数据页。

尾部用于存储旧的最少访问的数据页

如下图:Buffer Pool List

算法会将频繁访问的数据页放在新的子列表;最少访问的记录存放在旧列表,并逐渐淘汰。

通常 LRU 算法按如下方式运行:

buffer pool 总量的 3/8 会分配给旧列表。

列表的中间包括新列表的尾部和旧列表的头部。

当 InnoDB 读入一个新的数据页时,会先将其插入列表中间(旧列表的头部)。

旧的子列表数据访问会改变其数据特性,并将其移动到新的子列表头部(预读操作除外)。

随着数据库操作的执行,buffer pool 中未被访问的页数据会逐渐移动到列表的尾部,并淘汰。

通常情况下,被访问的数据会转移到新的子列表,这样就能在 buffer pool 中待更长的时间。一些特定的情景,如 mysqldump 操作导致的表扫描或者没有附加 where 条件的 select 查询会导致大量的数据写入 buffer pool,并淘汰旧的记录。但是这些新的记录可能永远不会被使用。

2、Change Buffer

change buffer 用于缓存那些不在 buffer pool 存储的二级索引页数据变化。并最终会合并到 buffer pool(当这些页数据被其它读操作载入后)。

如下入示意 Change Buffer:

和聚簇索引不同的是,二级索引通常都非唯一,并且写入顺序随机。同样的,删除和更新操作可能会影响不相邻的多个索引页数据。因此,在其它读操作将受影响的索引页数载入 bufer pool 时合并缓存的索引变更,可以避免再次从磁盘随机IO读取二级索引页数据。

purge operation 会周期性的把更新的页数据批量写入磁盘,这样比即时单条写入更有效率。

当涉及二级索引变更记录比较多时,Change buffer 数据合并可能会花费几个小时。在此期间,磁盘 IO 会增加,进而会影响磁盘密集型查询。

在内存中,change buffer 会占用一部分的 buffer pool 存储使用。在磁盘里,change buffer 是 system tablespace 的一部分,用以存储数据库服务器关机时产生的索引变化数据。

3、 Adaptive Hash Index

自适应哈希索引使得 InnoDB 支持基于内存的数据库,通过 innodb_adaptive_hash_index 配置启用。

基于当前的搜索模式,哈希索引使用索引键前缀来构建。前缀可长可短,根据实际查询需求而定。

4、Log Buffer

存储内存日志数据,用于磁盘日志文件数据写入。配置:innodb_log_buffer_size。默认大小 16MB。log buffer 的数据会周期性的刷盘。较大的 log buffer 有利于较大的事务日志数据写入需求。对于执行大批量更新、写入或删除操作的事务可以适当调高 log buffer 以减少磁盘IO。

二、磁盘存储结构

1、Index 索引

a)聚簇索引及二级索引

基于 InnoDB 引擎的表使用一种称之为聚簇索引的特殊索引来存储行数据。通常情况下,聚簇索引等同于主键索引。

InnoDB 会使用表上定义的主键来作为聚簇索引,如果当前表没有能够作为主键的列(数据逻辑唯一非空的单列或者多列组合),则可以添加自增列作为非业务主键。
如果表未定义主键,则 InnoDB 会使用首个唯一索引(所有列非空)作为聚簇索引。
如果表既没有主键也没有合适的唯一索引,则 InnoDB 会为表创建一个隐藏的聚簇索引 GEN_CLUST_INDEX,该索引基于 InnoDB 为表自动添加的包含行ID值的列,所有表数据会基于该ID值排序。行ID值是一个6字节数值,会随着数据的插入单调递增,因此基于此列排序的表在物理上保持着数据插入顺序。

除了聚簇索引,其它的索引都是二级锁索引,二级索引除了设置的索引列外,还包含主键,最终 InnoDB 都要通过主键来查找聚簇索引里的数据。

如果主键过长,那么二级索引就会占用更大的空间,所以,通常我们都建议设置较短的主键。

B 树索引使用:

支持列 =、>、>=、<、<= 及 BETWEEN 操作。
like 操作支持:like 后面的参数需要为常量并且不能以通配符起始。

//可以命中索引
SELECT * FROM tbl_name WHERE key_col LIKE 'Patrick%';
SELECT * FROM tbl_name WHERE key_col LIKE 'Pat%_ck%'; //无法使用索引
SELECT * FROM tbl_name WHERE key_col LIKE '%Patrick%';
SELECT * FROM tbl_name WHERE key_col LIKE other_col;

对于 is NULL 条件,如果条件列有索引,则查询会使用到索引。  
对于多列复合索引,如果要使用它们,则在每一个 and 条件分组里都必须使用它们:

如下使用到了索引:

... WHERE index_part1=1 AND index_part2=2 AND other_column=3 /* index = 1 OR index = 2 */
... WHERE index=1 OR A=10 AND index=2 /* optimized like "index_part1='hello'" */
... WHERE index_part1='hello' AND index_part3=5 /* Can use index on index1 but not on index2 or index3 */
... WHERE index1=1 AND index2=2 OR index1=3 AND index3=3; 如下未使用到索引 /* index_part1 is not used */
... WHERE index_part2=1 AND index_part3=2 /* Index is not used in both parts of the WHERE clause */
... WHERE index=1 OR A=10 /* No index spans all rows */
... WHERE index_part1=1 OR index_part2=10

一些特殊情况,如优化器测算使用索引会需要访问表中大量的数据,那么即使条件列命中了索引使用条件也不会使用索引。

b)InnoDB 索引物理结构

除了空间索引(基于 R-trees,用以组织存储多维数据),InnoDB 索引都是基于 B-tree 结构。数据存储于树的叶子结点。

索引数据页默认大小为 16KB,可以通过 mysql 实例初始化时的 innodb_page_size 参数来调整。

当向聚簇索引插入新的记录时,InnoDB 会保留1/16页空间用以应对将来可能的插入和更新。如果是顺序插入,则索引页空间会保持差不多15/16大小。如果是随机的,则页空间大小会在1/2 到 15/16之间。一般低于1/2(MERGE_THRESHOLD 配置)会触发索引树压缩。

c)Sorted Index Builds

InnoDB 使用 bulk load 方式执行索引创建或重建,我们称之为 Sorted index build(不支持空间索引)。

索引重建通常分为三步:

扫描聚簇索引,生成索引记录并添加到 sort buffer。sort buffer 满了之后,记录会被排序并写入一个临时的中介文件
随着多个第一步这个过程写入数据到临时中介文件,文件里的索引记录会执行合并。
排序的索引记录写入 B-tree。

在 Sorted index builds 引入之前,B-tree 索引写入使用特定的写入API。首先需要打开一个 B-tree 游标并找到写入位置,然后使用 optimistic  方式将索记录写入 B-tree。当遇到当前写入页满时,optimistic 会执行相应的 B-tree 节点的分裂或者合并操作来满足写入空间需求。这种自上而下的构建方式存在一定的缺点,包括寻址及经常性的节点分裂及合并成本。

Sorted index builds 基于自底而上的方式来构建索引。从 B-tree 每层最右侧的叶子节点开始,基于索引记录顺序写入。当一个节点页写满,则向其父节点添加一个新的子节点用于新的写入。

2、table space 表空间

system table space

用以存储包括 InnoDB data dictionary、the doublewrite buffer、the change buffer 及 undo logs,也可以存储用户主动创建于此的表及索引数据。

可以有一个或多个数据存储文件,默认为一个 ibdata1,大小和数量可以通过 innodb_data_file_path 进行配置。

File-Per-Table Tablespaces

file-per-table tablespace 包括一张表的数据和索引,以单个数据文件形式存储在文件系统。

InnoDB 默认在 file-per-table tablespaces 创建表,可以通过 innodb_file_per_table 配置变更,禁用则会导致在  system tablespace 创建。

file-per-table tablespace 数据文件形式 table_name.ibd,存储于 MySQL data 文件夹下。

优势:

表 truncate 或者 drop 操作后,磁盘空间会返还操作系统。

操作表复制时的空间额外使用在共享表空间不会返还操作系统。

TRUNCATE TABLE 操作性能在 file-per-table tablespaces 表现更优。

File-per-table tablespace 数据文件可以创建于不同的存储设备。这使得我们在 IO 优化,空间管理及备份策略等方面有更灵活的操作。

可以从其它 MySQL 实例中倒入表数据。

file-per-table tablespaces 中创建的表使用 Barracuda 文件格式。支持 DYNAMIC 及 COMPRESSED 行格式特性。

减少崩溃恢复使用时间及增加成功率。

基于 MySQL Enterprise Backup 备份和恢复单标数据更加快捷,并且不影响其它表使用。

可以通过监控表空间数据大小来实现表大小的监控。

通常 Linux 系统不允许并发写入同一个文件,当 innodb_flush_method 设置为 O_DIRECT 时,可能会提升数据库性能表现。

单个 file-per-table tablespace  64TB 空间限制,相较于共享表空间可以存储更多的数据。

劣势:

每张表都会存在未使用的空间,只能用于本表记录使用,管理不当会造成空间的浪费。

系统 fsync 操作只针对单个文件,多表写操作会导致系统 fsync 操作增加

mysqld  需要对每个 file-per-table tablespace 保持一个打开的文件句柄,如果表数量比较多多的话,可能影响数据库性能。

需要更多的文件描述符。

可能的文件及内存碎片问题会影响 DROP TABLE 及 table scan 操作。如果可以很好的管理碎片问题那么这些操作的性能反而会更好

drop 表时会对 buffer pool 进行扫描,扫表会伴随 broad internal lock,,如果 buffer pool 很大的话耗时会很长,进而会影响其它数据库操作。

自动扩展,空间增长不受 innodb_autoextend_increment 配置控制,期初增长因子很小,一段时间会以 4MB 大小增量扩展。

 General Tablespaces  共享的 InnoDB 表空间。
 Undo Tablespaces  undo logs 存储。
 The Temporary Tablespace  非压缩的,用户创建的临时表及磁盘上的内部临时表存储。

3、double buffer

具体介绍见前文链接:mysql 优化之 doublewrite buffer 机制

4、Redo Log

redo log 是一种基于磁盘的数据结构,用于修正数据库崩溃恢复期间未完成事务造成的数据脏写。

redo log 磁盘存储数据文件为 ib_logfile0 和 ib_logfile1,MySQL 以环形方式写入。

配置修改:1、配置文件 my.cnf;2、大小 innodb_log_file_size;3、数量:innodb_log_files_in_group.

5、Undo logs

记录单个事务中的一系列记录变更,用以恢复对聚簇索引记录的最新变更。如果有其它事务基于一致性读操作需要查看原始数据,可以从 undo log 记录里查询。

6、InnoDB Data Dictionary

包括一系列系统表,存储包括表、索引及表列等相关元数据,物理存储在系统表空间。由于一些历史原因,data dictionary metadata 部分存储在 InnoDB 表空间文件 (.frm files)。

MySQL InnoDB Architecture 简要介绍的相关教程结束。

《MySQL InnoDB Architecture 简要介绍.doc》

下载本文的Word格式文档,以方便收藏与打印。