InnoDB 存储引擎的底层逻辑架构

657 阅读12分钟

我们都知道 MySQL 数据库有很多个存储引擎,其中另我们印象深刻的应该是 InnoDB 存储引擎,它从 MySQL 5.5 之后就是默认的存储引擎,它有支持事务、行级锁、MVCC 以及外键等优点。

那么你知道InnoDB存储引擎的底层逻辑架构吗?下面我们就来聊一下InnoDB存储引擎。

InnoDB存储引擎主要由两个部分组成,分别是内存架构磁盘架构,这两个部分都有自己不可或缺的功能。下面我们就通过一张图来详细了解一下这两个部分。

image.png

内存架构

内存架构(英文名称:In-Memory Structures),在InnoDB存储引擎中主要包括四个部分,分别是自适应哈希索引、Buffer poolChange bufferLog Buffer四个部分。

1. 自适应哈希索引

首先我们来聊聊自适应哈希索引,自适应哈希索引的英文名称:Adaptive Hash Index它的设计目的是想让 MySQL 数据库像内存数据库一样高效,同时不会丢掉事务、行锁以及外键等特性

它并不是我们人为去创建的,而是InnoDB存储引擎通过索引监控机制去自动创建的,也就是说如果InnoDB存储引擎监控到自适应哈希索引可以提高查询速度,随即InnoDB存储引擎会自动为本次查询创建自适应哈希索引。命中了自适应哈希索引的查询就不会触发全表扫描,而是直接通过索引拿需要的数据,这样就可以提高数据库的查询速度。

但是自适应哈希索引并不是任何情况下都可以使用,例如:link '%xxx',这是因为 link 前置百分号查询本身就需要全表扫描,所以用与不用索引的结果都是一样的,用索引反而会多此一举,因此这种情况下不需要创建自适应哈希索引。

2. Buffer pool

Buffer pool(中文名称:缓冲池),是 MySQL 数据库中最重要的一个部分。在数据库启动之时,首先会初始化这块内存区域,它占用了 MySQL 数据库总内存空间的80%以上。详细情况可以通过show engine innodb status\G来查看:

mysql> show engine innodb status\G
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137428992
Dictionary memory allocated 301572
Buffer pool size   8191
Free buffers       6916
Database pages     1252
Old database pages 442
Modified db pages  0
Pending reads      0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 258, not young 1
0.00 youngs/s, 0.00 non-youngs/s
Pages read 320, created 938, written 3279
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 1252, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]

它的主要作用是提高数据库查询的效率,其中主要使用了LRU算法,下面我们一起来详细了解一下LRU算法。

image.png

MySQL数据库中,LRU算法的底层主要是一个链表。不过该链表被分为了两个区域,分别是新子链表和旧子链表,而且新子链表占用总空间的5/8,旧子链表占用总空间的3/8。其主要实现的步骤如下:

image.png

  • 第一步:假设我们读取数据2,这个时候恰好数据2在新子链表中,这个时候,会将数据2调换至新子链表的开头。
  • 第二步:如果查询一条buffer pool中没有的数据时,MySQL数据库将在磁盘中读取出该条数据数据X,并且插入新子链表后面,同时会淘汰旧子链表中的数据N

说到这里,可能就会有朋友问了,既然新数据移到链表的最前方,排列在最后面的数据直接淘汰,那么为什么还需要一个新子链表和旧子链表呢?

这个时候我们设想一下,假设我们查询一个比较大的数据,可能会占满所有的Buffer pool内存空间,按照我们理解的淘汰策略,这个时候会一下子将所有的数据全部淘汰。而这个时候正在高速运转的数据库会将所有的查询全部作用于磁盘,那将会导致系统磁盘 IO 急剧升高且数据库反应缓慢,最终会导致用户体验下降。

这个时候我们再看,如果把所有新查询的数据全部存放于新子链表中,查询的数据最多把新子链表中的空间全部占满而旧子链表中仍然保留着之前的数据,对于高速运转的数据库来讲,就不会导致系统磁盘 IO 急剧升高和数据库反应缓慢了。这也正是新旧子链表设计的初衷。

Buffer Pool存储块中还保留有一个小内存块,即Change buffer。下面我们就来聊聊这块内存是用来做什么的。

3. Change buffer

Change Buffer的另一个名字叫“写缓存”。见名知意,Change Buffer主要的功能是记录数据库的数据修改操作的结果的。主要目的是提高数据库的写性能

下面我们就来详细分析一下,数据修改操作的步骤。

  • 第一步:修改一条数据时,首先判断该条数据是否存在于Buffer Pool之中。

    • 如果在,直接修改Buffer Pool中的相关数据。
    • 如果不在,首先在磁盘中读取该条数据到Change Buffer之中,而后在Change Buffer中修改该数据,同时写入Redo Log之中(为了防止数据丢失),等下一次查询该条数据时,合并至Buffer Pool中。
  • 第二步:Change Buffer中数据修改之后,什么时候合并数据呢?

    • 第一种方式:当修改的这条数据被查询的时候,合并到Buffer Pool
    • 第二种方式:MySQL 数据库中的Master Thread合并(周期默认:10s)。
    • 第三种方式:当 MySQL 数据库关闭时,通过Redo Log合并到磁盘中。

Change Buffer之所以这样设计,是因为对于高速运转的 MySQL 数据库来讲,如果每一次修改都修改磁盘同时又修改Buffer Pool中的内容的话,对于 MySQL 数据库来讲代价太大了,磁盘的 IO 也会非常高,最终会导致 MySQL 数据库运行缓慢。那么,修改数据时使用Change Buffer相当于在内存中修改数据,并且保存在内存中,当数据库空闲时才会写入磁盘,这样既能够达到修改数据的目的,又能够降低数据库对于系统的性能要求,进而提高数据库的性能。

上面我们提到,Change Buffer修改完成之后,会修改redo log中的数据,那么接下来我们就来了解一下Log Buffer

4. Log Buffer

我们设想一下,如果在Change Buffer修改完数据之后,仅仅保存在内存中,那么如果这个时候数据库宕机,也就意味着我们刚刚修改的数据也随即丢失,而这一点是不能被允许的。

怎么解决这个问题呢?MySQL 给我们提供了一种写日志的方案,也就是说,修改完的数据会保存到一个叫Redo Log(具体请参考下方的Redo Log部分)的日志中。它是一个物理日志,当数据宕机时,它会将数据直接保存在磁盘之上;当数据库开启时,自动写入到数据库的磁盘中,以至于数据不会丢失。

上方我们提到了,Redo Log是一个物理日志,如果把大量的数据直接写进磁盘,还是会导致数据库性能低下,我们用一个Log Buffer来保存需要写入Redo log的数据,这样有利于提高数据库的性能。

这个时候你可能会问:那Change Buffer为什么不直接写入磁盘呢?

具体情况是这样的,MySQL 数据库在系统磁盘上保存的数据是有序的(典型就是按照主键 ID),如果每一次修改数据直接操作磁盘的话,会导致很多数据的位置发生更改(也就是我们常说的:随机 IO),但是Redo log中保存的数据是无序的,随意不会产生随机 IO,所以使用Redo log暂时保存数据是确保数据不丢失时的最好办法。

聊完InnoDB存储引擎的内存架构之后,接下来我们再来了解一下InnoDB存储引擎的磁盘架构。

磁盘架构

对于InnoDB存储引擎来说,磁盘架构最重要的就是表空间了。InnoDB存储引擎的表空间主要分为:系统表空间、独立表空间、普通表空间、Undo表空间以及临时表空间。

下面我们一起来详细聊聊InnoDB存储引擎的磁盘架构中的各个表空间。

1. 系统表空间

系统表空间是InnoDB存储引擎中最重要的表空间之一,它的主要作用是存储InnoDB数据字典、双写缓冲、更改缓存以及撤销日志。

系统表空间一般存放于 MySQL 数据库目录中,名称为:ibdata1。系统表空间一般不一定只有一个,也可能有多个,系统表空间的大小和数量由innodb_data_file_path控制。具体如下:

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)

在这里特别需要说明的是,InnoDB 数据字典在 MySQL 8.0 版本以后合并至 MySQL 数据字典中了,不再存储在系统表空间中了。

这个时候你可能会问,MySQL 数据表中的数据存放于哪里呢?下面我们就来聊一聊这个问题。

2. 独立表空间

对于innodb存储引擎来说,我们通常创建数据表的时候,会在 MySQL 数据目录中创建两个文件,分别是.ibd.frm两个文件。.ibd文件主要用来存储表数据,而.frm文件主要用来存储索引。

这种做法可以将所有的数据表分开管理,也能够实现快速数据迁移,当数据出现故障之时也可以提高数据恢复的成功率。不过这样的做法又会增加磁盘的碎片,对系统处理表文件的性能有一定的影响。

3. 普通表空间

普通表空间的本质其实就是一个共享的表空间。其具体文件在 MySQL 数据库的数据目录中是以.ibd结尾的文件。跟系统表空间类似,它支持所有 MySQL 数据库中的数据表的结构,它是将数据库的一些元数据保存在内存之中,进而能够减少独立表空间对于内存的消耗。

4. Undo 表空间

Undo 表空间主要是用来保存撤销日志(即:Undo Log)的空间。它默认情况下存储在 MySQL 数据库的根目录。我们可以通过以下方式来查看:

mysql> SHOW VARIABLES LIKE 'innodb_undo_directory';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_undo_directory    | ./    |
+--------------------------+-------+
4 rows in set (0.00 sec)

MySQL 8.0版本之后,undo 表空间会在 MySQL 数据库的数据根目录生成 undo_001 和 undo002 共两个文件。

5. 临时表空间

临时表空间主要是用来保存数据库会话中的临时数据的。在 MySQL 数据库的数据根目录中保存以ibtmp1命名的文件。最主要的是我们在使用 join 连表查询的时候,会在临时表空间内创建临时数据表用来辅助查询。我们可以通过以下方式来查看临时表空间的配置:

mysql> SELECT @@innodb_temp_data_file_path;
+------------------------------+
| @@innodb_temp_data_file_path |
+------------------------------+
| ibtmp1:12M:autoextend        |
+------------------------------+
1 row in set (0.00 sec)

总结

InnoDB存储引擎是 MySQL 数据库中最重要的一个存储引擎之一。今天我们一起通过它的内存架构和磁盘架构深入地了解了它的底层架构。

在内存架构中,自适应哈希索引有利于提高查询速度;Buffer pool主要提供了一个内存池,将经常查询的数据存放于内存中,这样做有利于提高数据库的查询性能和降低系统的磁盘 IO;Change buffer主要是将修改好的数据存放于内存之中,下一次查询的时候合并到Buffer pool之中,这样做的好处是可以降低修改数据时的磁盘 IO,进而提高数据库的性能;Log Buffer是将所有修改的数据存放在其中,之后写入到Redo Log之中,防止数据丢失。

在磁盘架构中,系统表空间是用来修改和撤销日志的地方,之前的数据库版本中还存放InnoDB数据字典以及双写缓冲;独立表空间主要是用来存储表数据和索引的地方;普通表空间是一个共享的表空间,能够减少独立表空间对于内存的消耗;Undo 表空间主要作用于事务回滚的,在使用未提交之前,用来保存原来的数据,一旦事务回滚则用 Undo 表空间中的内容替换修改过后的数据,进而达到回滚的目的;临时表空间主要是一个过渡的表空间,通常的一些操作需要有这种过渡来辅助操作,例如连表查询。

从内存架构到磁盘架构,InnoDB存储引擎为我们提供了一个高性能、高安全的数据库存储引擎。通常在实际应用过程中,InnoDB存储引擎是我们的首选存储引擎,但是在使用过程中一定要把Buffer pool的空间设置得足够大,这样有利于提高数据的查询性能。