文章目录
- 内存结构
-
- [Buffer Pool](#Buffer Pool)
- [Change Buffer](#Change Buffer)
- [自适应哈希索引 Adaptive Hash Index](#自适应哈希索引 Adaptive Hash Index)
- [Log Buffer](#Log Buffer)
- 磁盘结构
-
- [System Tablespace](#System Tablespace)
- [File-Per-Table Tablespace](#File-Per-Table Tablespace)
- [General Tablespace](#General Tablespace)
- [Undo Tablespace](#Undo Tablespace)
- 临时表空间
- [Double Write Buffer 文件](#Double Write Buffer 文件)
首先,先给出官网的一张InnoDB的架构图。InnoDB存储引擎主要由两个部分组成,分别是内存架构和磁盘架构
内存结构
在InnoDB中内存架构主要包括四个部分,分别是Buffer pool、Change buffer、自适应哈希索引、和Log Buffer。
Buffer Pool
https://dev.mysql.com/doc/refman/8.4/en/innodb-buffer-pool.html
InnoDB 存储引擎是基于磁盘存储的,使用InnoDB时,所有数据,包括列上的任何和所有索引,都存储在磁盘上的Page中。由于 CPU 速度与磁盘速度之间的差距,基于磁盘的数据库系统通常使用内存缓冲区技术来提高数据库的整体性能。
当MySQL执行和完成查询时,它需要能够将这些数据的子集加载到RAM中,以便更快地访问。MySQL使用的就是这个叫做Buffer Pool的东西,Buffer Pool是主存中的一个区域,InnoDB在访问表和索引数据时将其缓存在该区域。Buffer Pool允许直接从内存访问频繁使用的数据,从而加快了处理速度。在一些数据专用服务器上,高达80%的物理内存通常分配给Buffer Pool。
Buffer Pool的大小可以使用innodb_buffer_pool_size
配置选项进行配置。当MySQL运行并读取行和索引时,它会从磁盘中读取Page并将其放入RAM的这个区域。
了解如何利用缓冲池将频繁访问的数据保存在内存中是MySQL调优的一个重要方面。
通常,这个池越大数据库的性能就越好,直到池大小=数据集的大小。如果数据的大小大于池中的可用空间,MySQL最终将不得不从缓冲池中删除Page,为加载新的Page腾出空间。MySQL为此使用了一种改进的LRU算法。
由于这种结构,当MySQL开始执行B树索引查找时,最初必须从磁盘加载许多Page,这个过程是一种缓存预热。然而,随着它处理越来越多的查询,这些Page中的许多最终可能会驻留在Buffer Pool中,这意味着每个B树节点的访问和比较执行得更快,因为它主要是在内存中操作而不是磁盘。
Change Buffer
https://dev.mysql.com/doc/refman/8.4/en/innodb-change-buffer.html
InnoDB 从 1.0.x 版本开始引入了 Change Buffer。Change Buffer主要的功能是记录数据库的数据修改操作的结果的,目的是为了提高数据库的写性能。因为 InnoDB 的辅助索引不同于聚集索引的顺序插入,如果每次修改二级索引都直接写入磁盘,则会有大量频繁的随机 IO。
基本工作原理
Change Buffer会将对非唯一辅助索引Page的操作缓存下来,如果辅助索引页已经在缓冲区了,则直接修改;如果不在,则先将对这些Page的更改保存到 Change Buffer。当对应辅助索引页读取到缓冲区时,才将 Change Buffer 的数据合并到真正的辅助索引页中,以此减少辅助索引的随机 IO,并达到操作合并的效果。缓冲的更改可能是由INSERT、UPDATE或DELETE操作(DML)引起的,稍后当页面通过其他读取操作加载到缓冲池中时,这些更改会被合并。
下面我们就来详细分析一下,数据修改操作的步骤:
- 第一步:修改一条数据时,首先判断该条数据是否存在于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就相当于在内存中修改数据,并且保存在内存中,当数据库空闲时才会写入磁盘,这样既能够达到修改数据的目的,又能够降低数据库对于系统的性能要求,进而提高数据库的性能。
在 MySQL 5.5 之前 Change Buffer 其实叫 Insert Buffer,最初只支持 INSERT 操作的缓存,随着支持操作类型的增加,改名为 Change Buffer,现在 InnoDB 存储引擎可以对 INSERT、DELETE、UPDATE 都进行缓冲,对应着:Insert Buffer、Delete Buffer、Purge buffer。
自适应哈希索引 Adaptive Hash Index
https://dev.mysql.com/doc/refman/8.4/en/innodb-adaptive-hash.html
InnoDB的索引是B+树结构,B+树索引查找比对所有元素进行线性扫描要快得多。命中索引的话查找大概时O(logn),并不是O(1),因为一次查找通常需要多次比较和页面加载。那么什么数据结构支持O(1)的查询呢?哈希表。MySQL创建索引时可以选择是使用hash索引,但是InnoDB其实是不支持hash索引的。
sql
CREATE INDEX alias_index ON user(username) USING HASH;
实际上创建的还是BTree索引。
自适应哈希索引是MySQL的一个优化方式,在内存中进行一个hash查找。如果hash索引里有,则不走B+树查找了
此功能可用于加速已经很快的B树查找,从而加速将这些索引作为查询计划一部分的查询的性能。这充当MySQL执行和内存缓冲池之间的一层。
自适应哈希索引并不是人为去创建的,而是InnoDB存储引擎通过索引监控机制去自动创建的,无法人为控制。同时自适应哈希索引是在运行时构建的,它的使用会根据工作负载的特点进行调整。如果MySQL观察到在B树索引中反复查找特定值,则可以使用完整值或值的前缀创建自适应哈希索引中的条目。对于将来查找此相同值(这是可能的,因为MySQL观察到它的重复使用),它将使用自适应哈希索引而不是B+树。自适应哈希索引的键是基础索引的值(或值前缀)。这些值是指针,指的是该值的数据在InnoDB缓冲池中的位置。
优缺点
- 不支持范围查询
- 不能人工干预
- 并不是任何情况下都可以使用,例如:like '%xxx',这是因为 like 前置百分号查询本身就需要全表扫描,所以用与不用索引的结果都是一样的,用索引反而会多此一举,因此这种情况下不需要创建自适应哈希索引。
- 一致性:自适应哈希索引相当于将Buffer Pool的数据又缓存了一部分,更新时会涉及到一致性问题,所以MySQL内部必然是保证了自适应哈希索引的数据和B+树上数据的同步更新的,会增加性能开销
- 维护自适应哈希索引也会有开销
使用场景
对于某些工作负载,哈希索引查找的加速远远超过了监控索引查找和维护哈希索引结构的额外工作。在繁重的工作负载下,对自适应哈希索引的访问有时会成为争用的来源,例如多个并发连接。使用LIKE运算符和%通配符的查询也往往不会受益。对于不受益于自适应哈希索引的工作负载,关闭它可以减少不必要的性能开销。因为很难提前预测自适应哈希索引是否适合特定的系统和工作负载,所以考虑在启用和禁用它的情况下运行基准测试。
关闭自适应哈希索引
自适应哈希索引中的指针仅指向缓冲池中的数据。因此Buffer Pool需要足够大,以便自适应哈希索引能够启动。如果Buffer Pool很小,并且发生了很多驱逐,则不值得使用。MySQL能够根据在缓冲池中观察到的行为自动调整其对自适应哈希索引的使用,也就是自适应这3个字的作用。如果使用条件不合适(重复查找次数少、Buffer Pool小等),MySQL将减少或消除其使用。
虽然它可以加快查询速度,但维护这个特殊的哈希索引会有一些开销。该功能可以通过innodb_adapter_hash_index
配置选项启用或禁用。默认情况下,它通常处于启用状态,可以使用innodb_adapter_hash_index=0
禁用它。
或者启动mysql server端时通过参数--innodb-adaptive-hash-index
进行
Log Buffer
如果在Change Buffer修改完数据之后,仅仅保存在内存中,那么如果这个时候数据库宕机,也就意味着我们刚刚修改的数据也随即丢失,而这一点是不能被允许的。
怎么解决这个问题呢?
MySQL 给我们提供了一种写日志的方案,也就是说,修改完的数据会保存到一个叫Redo Log的日志中。它是一个物理日志,当数据宕机时,它会将数据直接保存在磁盘之上;当数据库开启时,自动写入到数据库的磁盘中,以至于数据不会丢失。
如果把大量的数据直接写进磁盘,还是会导致数据库性能低下,我们用一个Log Buffer来保存需要写入Redo log的数据,这样有利于提高数据库的性能。
磁盘结构
MySQL是基于磁盘存储的数据库,所有数据最终都会存放在磁盘上
System Tablespace
系统表空间的主要作用是存储InnoDB数据字典、双写缓冲、更改缓存以及撤销日志。
系统表空间一般存放于 MySQL 数据库目录中,名称为:ibdata1。
sql
mysql> SHOW VARIABLES LIKE 'datadir';
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| datadir | /var/lib/mysql/ |
+---------------+-----------------+
在数据目录文件夹下可以看到ibdata1文件
系统表空间一般不一定只有一个,也可能有多个,系统表空间的大小和数量由innodb_data_file_path控制。具体如下:
sql
mysql> SHOW VARIABLES LIKE 'innodb_data_file_path';
+-----------------------+------------------------+
| Variable_name | Value |
+-----------------------+------------------------+
| innodb_data_file_path | ibdata1:12M:autoextend |
+-----------------------+------------------------+
注意
InnoDB 数据字典在 MySQL 8.0 版本以后合并至 MySQL 数据字典中了,不再存储在系统表空间
File-Per-Table Tablespace
File-Per-Table表空间包含单个InnoDB表的数据和索引,并存储在文件系统中的单个数据文件中。
对于innodb存储引擎来说,通常创建数据表的时候,会在 MySQL 数据目录中创建两个文件,分别是.ibd和.frm两个文件。.ibd文件主要用来存储表数据,而.frm文件主要用来存储索引。MySQL8.0以后没有.frm了,元数据都存在系统表空间里。
这种做法可以将所有的数据表分开管理,也能够实现快速数据迁移,当数据出现故障之时也可以提高数据恢复的成功率。不过这样的做法又会增加磁盘的碎片,对系统处理表文件的性能有一定的影响。
innodb_file_per_table
InnoDB创建表时默认情况下将表创建在文件表空间,即一个表对应一个文件。这个行为可以通过配置innodb_file_per_table
变量来修改
sql
mysql> SELECT @@innodb_file_per_table;
+-------------------------+
| @@innodb_file_per_table |
+-------------------------+
| 1 |
+-------------------------+
1 row in set (0.00 sec)
禁用innodb_file_per_table
会导致innodb在系统表空间中创建表,也就是表数据会放到ibdata1文件中
innodb_file_per_table设置可以在选项文件中指定,如下所示:
ini
[mysqld]
innodb_file_per_table=ON
也可以在运行时使用SET GLOBAL语句进行配置:SETGLOBAL innodb_file_per_table=ON;
,在运行时更改设置需要足够的权限来设置全局系统变量。
General Tablespace
https://dev.mysql.com/doc/refman/8.4/en/general-tablespaces.html
翻译为通用表空间,通用表空间是使用CREATE tablespace语法创建的共享InnoDB表空间。其具体文件在 MySQL 数据库的数据目录中是以.ibd结尾的文件。跟系统表空间类似,它支持所有 MySQL 数据库中的数据表的结构,它是将数据库的一些元数据保存在内存之中,进而能够减少独立表空间对于内存的消耗。
Undo Tablespace
Undo 表空间主要是用来保存撤销日志Undo Log的表空间。它默认情况下存储在 MySQL 数据库的根目录。我们可以通过以下方式来查看:
sql
mysql> SHOW VARIABLES LIKE 'innodb_undo_directory';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_undo_directory | ./ |
+-----------------------+-------+
1 row in set (0.00 sec)
在MySQL 8.0版本之后,undo 表空间会在 MySQL 数据库的数据根目录生成 undo_001 和 undo002 共两个文件,如下所示
临时表空间
临时表空间主要是用来保存数据库会话中的临时数据的。在 MySQL 数据库的数据根目录中保存以ibtmp1命名的文件。最主要的是我们在使用 join 连表查询的时候,会在临时表空间内创建临时数据表用来辅助查询。我们可以通过以下方式来查看临时表空间的配置:
sql
mysql> SELECT @@innodb_temp_data_file_path;
+------------------------------+
| @@innodb_temp_data_file_path |
+------------------------------+
| ibtmp1:12M:autoextend |
+------------------------------+
1 row in set (0.00 sec)
在数据目录可以看到临时表空间文件
Double Write Buffer 文件
当发生数据库宕机时,可能存储引擎正在写入某个Page到表中,而这个页只写了一部分,比如 16KB 的页,只写了前 4KB,之后就发生了宕机。虽然可以通过日志进行数据恢复,但是如果这个页本身已经发生了损坏,再对其进行重新写入是没有意义的。因此 InnoDB 引入 Double Write Buffer 解决数据页的半写问题。
Double Write Buffer 大小默认为 2M,即 128 个数据页。其中分为两部分,一部分留给batch write,提供批量刷新脏页的操作,另一部分是single page write,留给用户线程发起的单页刷脏操作。
在对缓冲池的脏页进行刷新时,脏页并不是直接写到磁盘,而是会通过memcpy()函数将脏页先复制到内存中的 Double Write Buffer 中,如果 Double Write Buffer 写满了,那么就会调用fsync()系统调用,一次性将 Double Write Buffer 所有的数据写入到磁盘中,因为这个过程是顺序写入,开销几乎可以忽略。在确保写入成功后,再使用异步 IO 把各个数据页写回自己的表空间中。