目录标题
- 前言
- 白话
- 内存架构
- 1. 自适应哈希索引
- 自适应哈希索引的作用
- 自适应哈希索引的工作原理
- 自适应哈希索引与缓存的区别
- 启用和禁用自适应哈希索引
- 2. Buffer pool
- 3. Change buffer
- 下面我们就来详细分析一下,数据修改操作的步骤。
- 4. Log Buffer
- 磁盘架构
- 1. 系统表空间
- 2. 独立表空间
- 3. 普通表空间
- 4. Undo 表空间
- 5. 临时表空间
- 总结白话
前言
我们都知道 MySQL 数据库有很多个存储引擎,其中另我们印象深刻的应该是 InnoDB 存储引擎,它从 MySQL 5.5 之后就是默认的存储引擎,它有支持事务、行级锁、MVCC 以及外键等优点。
那么你知道InnoDB存储引擎的底层逻辑架构吗?下面我们就来聊一下InnoDB存储引擎。
白话
InnoDB存储引擎主要由两个部分组成,分别是**内存架构和磁盘架构**,这两个部分都有自己不可或缺的功能。
内存架构
内存架构(英文名称:In-Memory Structures),在InnoDB存储引擎中主要包括四个部分,分别是自适应哈希索引、Buffer pool、Change buffer和Log Buffer四个部分。
1. 自适应哈希索引
首先我们来聊聊自适应哈希索引,自适应哈希索引的英文名称:Adaptive Hash Index。它的设计目的是想让 MySQL 数据库像内存数据库一样高效,同时不会丢掉事务、行锁以及外键等特性。
它并不是我们人为去创建的,而是InnoDB存储引擎通过索引监控机制去自动创建的,也就是说如果InnoDB存储引擎监控到自适应哈希索引可以提高查询速度,随即InnoDB存储引擎会自动为本次查询创建自适应哈希索引。命中了自适应哈希索引的查询就不会触发全表扫描,而是直接通过索引拿需要的数据,这样就可以提高数据库的查询速度。
但是自适应哈希索引并不是任何情况下都可以使用,例如:link ‘%xxx’,这是因为 link 前置百分号查询本身就需要全表扫描,所以用与不用索引的结果都是一样的,用索引反而会多此一举,因此这种情况下不需要创建自适应哈希索引。
AHI的主要作用是通过将B-Tree索引转换为哈希索引,从而提高特定类型的查询性能。
自适应哈希索引(Adaptive Hash Index, AHI)是InnoDB存储引擎中的一种优化机制,是为了加速某些查询而动态创建的内存结构。AHI的主要作用是通过将B-Tree索引转换为哈希索引,从而提高特定类型的查询性能。
自适应哈希索引的作用
-
加速单值查找:
- 当InnoDB检测到某些查询经常访问某个B-Tree索引的叶节点时,它会自动创建一个哈希索引来加速这些查询。哈希索引通常比B-Tree索引更快,因为它们可以通过计算哈希值直接定位到数据,而不是像B-Tree那样需要进行多次磁盘I/O操作。
-
减少B-Tree索引的遍历:
- 对于频繁访问的单值查找,使用哈希索引可以显著减少B-Tree索引的遍历次数,从而提高查询性能。
-
自动管理和优化:
- AHI是自动管理的,InnoDB会根据查询模式和工作负载动态地创建和删除哈希索引。这使得AHI能够在不需要额外配置的情况下提供性能提升。
自适应哈希索引的工作原理
-
监测查询模式:
- InnoDB会监测对B-Tree索引的访问模式。如果发现某些查询频繁访问相同的B-Tree索引页,它会考虑创建哈希索引。
-
创建哈希索引:
- 当满足一定的条件时(例如,连续几次访问相同的B-Tree索引页),InnoDB会在内存中创建一个哈希索引。这个哈希索引是基于B-Tree索引的前缀构建的。
-
使用哈希索引:
- 一旦哈希索引创建完成,后续的查询就可以利用这个哈希索引来快速定位数据,而不需要再遍历B-Tree索引。
-
动态调整:
- 如果查询模式发生变化,不再频繁访问某个B-Tree索引页,InnoDB会自动删除相应的哈希索引,以节省内存资源。
自适应哈希索引与缓存的区别
- 缓存:缓存(如Buffer Pool)用于存储从磁盘读取的数据页,以便在后续访问时能够快速从内存中获取,减少磁盘I/O。
- 自适应哈希索引:AHI是一种特殊的内存结构,用于加速特定类型的查询,特别是那些频繁访问相同B-Tree索引页的查询。它并不存储完整的数据页,而是存储指向数据页的哈希索引。
启用和禁用自适应哈希索引
InnoDB提供了配置选项来控制是否启用自适应哈希索引:
- 启用:默认情况下,自适应哈希索引是启用的。可以通过设置
innodb_adaptive_hash_index=ON
来明确启用。 - 禁用:如果发现自适应哈希索引导致了性能问题或内存使用过高,可以通过设置
innodb_adaptive_hash_index=OFF
来禁用它。
自适应哈希索引是InnoDB存储引擎中的一种优化机制,用于加速频繁访问的单值查找查询。它通过将B-Tree索引转换为哈希索引来提高查询性能。AHI是自动管理的,可以根据查询模式动态创建和删除。虽然它不是传统意义上的缓存,但它确实有助于减少B-Tree索引的遍历次数,从而提高整体查询性能。
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算法。
在MySQL数据库中,LRU算法的底层主要是一个链表。不过该链表被分为了两个区域,分别是新子链表和旧子链表,而且新子链表占用总空间的5/8,旧子链表占用总空间的3/8。其主要实现的步骤如下:
- 第一步:假设我们读取数据2,这个时候恰好数据2在新子链表中,这个时候,会将数据2调换至新子链表的开头。
- 第二步:如果查询一条buffer pool中没有的数据时,MySQL数据库将在磁盘中读取出该条数据数据X,并且插入新子链表后面,同时会淘汰旧子链表中的数据N。
说到这里,可能就会有朋友问了,既然新数据移到链表的最前方,排列在最后面的数据直接淘汰,那么为什么还需要一个新子链表和旧子链表呢?
这个时候我们设想一下,假设我们查询一个比较大的数据,可能会占满所有的Buffer pool内存空间,按照我们理解的淘汰策略,这个时候会一下子将所有的数据全部淘汰。而这个时候正在高速运转的数据库会将所有的查询全部作用于磁盘,那将会导致系统磁盘 IO 急剧升高且数据库反应缓慢,最终会导致用户体验下降。
这个时候我们再看,如果把所有新查询的数据全部存放于新子链表中,查询的数据最多把新子链表中的空间全部占满而旧子链表中仍然保留着之前的数据,对于高速运转的数据库来讲,就不会导致系统磁盘 IO 急剧升高和数据库反应缓慢了。这也正是新旧子链表设计的初衷。
理解InnoDB Buffer Pool中的新旧子链表设计,需要从内存管理和数据访问模式的角度来考虑。Buffer Pool是InnoDB用来缓存数据页和索引页的内存区域,通过有效的管理可以显著提高数据库性能。新旧子链表的设计正是为了优化内存使用和减少磁盘I/O操作,从而提高系统的整体性能和稳定性。
为什么需要新旧子链表?
-
防止突发的大查询导致大量数据被淘汰。比如:大量的数据被淘汰,磁盘I/O急剧增加;
-
平滑数据淘汰过程
新子链表(New Sublist):用于存放最近访问的数据页。
旧子链表(Old Sublist):用于存放较长时间没有被访问的数据页。新旧子链表的设计有以下几个关键点:
- 新子链表的大小:新子链表通常只占整个Buffer Pool的一小部分(例如5%或10%),具体大小可以通过innodb_old_blocks_pct参数配置。
- 数据页的移动:当一个数据页第一次被访问时,它会被放入新子链表的头部。如果这个数据页再次被访问,它会被移到旧子链表的头部。
- 淘汰策略:当Buffer Pool空间不足时,首先从旧子链表的尾部开始淘汰数据页。只有当旧子链表为空时,才会从新子链表中淘汰数据页。
在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是循环使用的,并且是以顺序方式写入的,这比直接进行随机I/O要快得多。但是Redo log中保存的数据是无序的,随意不会产生随机 IO,所以使用Redo log暂时保存数据是确保数据不丢失时的最好办法。
关于“无序”这个描述:
- Redo log中的记录确实是按照事务提交的时间顺序写入的,而不是按照任何特定的表或索引的逻辑顺序。因此,可以说它是“无序”的,但这并不是说它完全杂乱无章;相反,它是严格按时间顺序组织的,这对于恢复操作来说是非常重要的。
- 由于Redo log记录的是对数据页的修改指令,所以它不需要关心数据在磁盘上的物理位置,只需关注如何将这些修改重新应用到相应的数据页上即可。
Redo Log 的作用和特点:
- 持久性保证:Redo log的主要目的是确保事务的持久性。当一个事务提交时,其变更首先会被记录到Redo log中。这样即使在数据还没有从内存写回到磁盘的情况下发生系统崩溃,也可以通过重做日志来恢复这些变更,从而避免数据丢失。
- 顺序写入:Redo log是按顺序追加的方式写入的,这意味着它不会产生随机I/O,而是进行连续的顺序写入。这比随机I/O要快得多,因为磁盘的顺序写性能通常远高于随机写性能。连续写入:每次写入新的日志条目时,都是从当前Redo log文件的末尾开始写入,不会覆盖已有的内容,也不会跳过任何位置。这种连续写入的方式避免了随机I/O,因为不需要磁头频繁移动到磁盘的不同位置。
- 临时缓冲:Redo log确实可以看作是一种临时缓冲机制,因为它先于实际的数据文件更新而被写入。一旦Redo log中的信息被安全地写入磁盘,事务就可以被认为是持久化的了。
关于随机I/O的问题:
- 随机I/O指的是必须在磁盘的不同位置之间跳转以读取或写入数据。这种操作相对缓慢,因为它涉及到磁头移动到正确的位置。
- 顺序I/O则是指连续地从磁盘的某个位置开始读取或写入数据,这种方式效率更高。
在MySQL的InnoDB存储引擎中,数据通常是按照主键ID进行物理排序并存储的。
聊完InnoDB存储引擎的内存架构之后,接下来我们再来了解一下InnoDB存储引擎的磁盘架构。
磁盘架构
对于InnoDB存储引擎来说,磁盘架构最重要的就是表空间了。InnoDB存储引擎的表空间主要分为:系统表空间、独立表空间、普通表空间、Undo表空间以及临时表空间。
下面我们一起来详细聊聊InnoDB存储引擎的磁盘架构中的各个表空间。
数据文件 (.ibd 或 .frm)
- .ibd 文件:对于使用独立表空间的表,每个表都会有一个.ibd文件,用来存储该表的数据和索引。
- .frm 文件:存储表的定义(如列名、类型等),这些信息是与存储引擎无关的。
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文件主要用来存储索引。
这种做法可以将所有的数据表分开管理,也能够实现快速数据迁移,当数据出现故障之时也可以提高数据恢复的成功率。不过这样的做法又会增加磁盘的碎片,对系统处理表文件的性能有一定的影响。
如果启用了innodb_file_per_table选项,每个表将有它自己的.ibd文件来存储数据和索引。
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的空间设置得足够大,这样有利于提高数据的查询性能。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们处理,核实后本网站将在24小时内删除侵权内容。