InnoDB存储引擎
文章目录
1. MySQL 逻辑架构
整体的逻辑架构分为 Server 层和引擎层两部分。其中 Server 层又细分为连接器和核心层,其中连接器那一层并非MySQL独有,大部分的C/S工具都有类似的架构(比如连接处理、授权认证、安全等),Server 层的第二层是 MySQL 独有的,MySQL 很多核心功能都在这一层,包括查询解析、分析、优化、缓存以及内置函数,还有所有跨存储引擎的功能也在这一层实现(包含存储过程、视图等等)。服务器和存储引擎通过 API 进行通信,存储引擎不会去解析 SQL,它只负责数据的存储和读取。具体逻辑架构如下:

2. 物理存储
在 MySQL 的文件系统中,MySQL 的每个数据库(也称为schema)都会以data目录下的一个子目录的形式存在(库名即文件夹名),然后各个数据库里面的表定义会以[table_name].frm的文件形式存储在所属的数据库目录中(但现在的8.0+版本中已经取消了*.frm文件,只有表空间文件.ibd)。另外 schema 的定义和数据库的定义差不多,在 MySQL 中,对 DATABASE 的操作完全可以使用 SCHEMA 来替换操作(如create database test和create schema test效果一样,都是创建一个test库)。
3. InnoDB 的结构
Innodb是目前MySQL最常用也是最推荐的一种存储引擎,它的架构(8.0版本)如下:

InnoDB 目前主要优势有:
- 它的DML(data manipulation language,即数据操作,主要是增删改查,注意和DDL的区别;DDL-data definition language,即数据定义语言,主要是创库、建表)操作遵ACID模式,且支持事务提交、回滚以及crash恢复;
- 行级锁和一致性读的特性提高了其并发能力;
- 它将数据存储到磁盘上且基于主键来优化查询,每个 InnoDB 表都有一个叫做聚簇索引的主键索引,这个索引用来组织数据以便减少根据主键进行搜索的 I/O;
- 为了维持数据完整性,InnoDB 支持外键约束。外键的存在可以保证在增、删、改时不会导致相关表的不一致;
3.1 InnoDB的内存结构
Innodb的内存结构主要有 Buffer Pool、Change Buffer、Adaptive Hash Index 以及 Log Buffer 几部分组成。
3.1.1 Buffer Pool
Buffer Pool 是 InnoDB 缓表和索引数据的主要内存区域,它允许直接从内存中频繁操作数据(这样的操作很快),在专用服务器上通常会将高达80%的物理内存会分配给 Buffer Pool。为了大容量的读取行为的性能,Buffer Pool 被分割为一个个的页(Page,页可以承载多个 row),为了提高缓存管理的效率,页的实现是基于 linked list,使用 LRU(least recently used) 算法的变体将很少使用的数据从缓存中移除。很多时候 MySQL 的调整往往都是基于如何更大程度的使用 Buffer Pool 来操作数据考虑的。
当需要空间去增加 page 到 buffer pool 中时,最近最少使用的 page 将会被驱逐(MySQL中将其称为老化,这些页也就是老年页),新的 page 将会被增加到列表的中间,这种中点插入法主要将列表分为2个子列表:
- 在 head 处,子列表是最近最经常使用的,这里的子列表称之为 new sublist;
- 在 tail 处,子列表是最近最不经常使用的,这里的子列表称之为 old sublist;

LRU 算法使得最近最常使用的保持在 new sublist 中,而 old sublist 中则是一些不被经常使用的 page,也是可能在不久后被驱逐那部分 page。如上图所示,默认 3/8 的 buffer pool 被划分为 old sublist,中点被定义在 new sublist 的尾部和 old sublist 的头部的相遇边界点,当 Innodb 读取 page 到 buffer pool 时,这些新 page 默认插入到被插入到中点处。
默认情况下,查询所需要的 page 将立刻被放到 new sublist 中,这部分的page也会活得更久(短时间内不会被驱逐)。扫表行为(如 mysqldump 操作或无 where 条件的 select)会带入大量数据到 buffer pool 中,将原来 old list 中大量的老 page 驱逐出 Buffer Pool,即使这些新 page 后面不被使用。相似的,由预读后台(read-ahead)线程加载且仅访问一次的 page 将移至 new sublist 的开头,这些场景中可以将频繁使用的 page 推到 old sublist 中,最终被驱逐,实际来讲,这些场景都需要进行优化,我们最终的目的是将频繁使用的 page 对象保留在 Buffer Pool 中,而且尽可能保留在 new sublist 中,因为这样查询会更快。
为了让Innodb更好、更快的工作,在配置 Buffer Pool 时可以做下面的一些优化:
- 理想状态下,可以将 buffer pool 设置为实际的内存大小,保留足够的内存处理无需过多的page,buffer pool 设置的越大,Innodb 就越表现的像基于内存的数据库,从磁盘读取一次数据,然后在后续读取期间从内存访问、操作数据。buffer pool 的配置是基于一块一块的(即chunks),一个块(chunk)默认是128M,buffer pool 的大小必须是
buffer_pool_chunk_six*buffer_pool_instances的整数倍,buffer_pool_chunk_six默认是128M,如mysqld --innodb-buffer-pool-size=8G --innodb-buffer-pool-instances=16就是将 Buffer pool 配置为8G(128M * 16 * 4 = 8G),当配置的值不满足上述的整数倍时,Innodb会自动调整为向上取整,如mysqld --innodb-buffer-pool-size=9G --innodb-buffer-pool-instances=16,9G不是整数倍,会被自动调整为10G。 - 对于足够内存的64位系统,可以将 buffer pool 分割为多个部分,以最大程度地减少并发操作之间的内存结构争用。
3.1.2 Change Buffer
Change Buffer 是一种特殊的数据结构,它缓存了那些不在 buffer pool 中的第二索引(即辅助索引)的 page 对象的改动,当一些insert、update、delete操作让缓存变化后,当这些 page 被其他的读行为加载到 buffer pool 中时,可能导致上述的改变最后合并到一起,这就是 change buffer(和它的名字一样,记录变化的缓存),结构如下:

和聚簇索引不一样,第二索引通常不是唯一的,且插入是相对随机的顺序,delete、update 可能会影响索引树种那些不相邻的第二索引的page。稍后,当其他操作将受影响的 page 读入 buffer pool 时,合并缓存的更改可以避免从磁盘将辅助索引页读入缓冲池所需的大量随机访问I/O。在系统基本处于空闲状态或在缓慢关机期间运行的清除操作会定期将更新的索引页写入磁盘,和立即写入磁盘相比,清除操作可以更高效的将一系列的索引值写入磁盘块。当有很多受影响的行和需要更新的辅助索引值时,change buffer 合并更改的行为可能会持续几个小时,并且在此期间,磁盘I/O会增加,与磁盘绑定的查询会变得十分缓慢。Change buffer 合并的行为可能会在事务提交后、甚至服务器重启后持续进行。
在内存方面,其实 change buffer 占据了 buffer pool 的内存的一部分。在磁盘方面,change buffer 是系统 tableSpace 的一部分,当数据库服务器关闭时,索引的更改将会被缓存。在 change buffer 中的缓存数据类型主要是由变量innodb change buffering维护管理。注意的是:Change buffer 不支持包含降序索引的列或者主键中包含降序的列。
3.1.3 adaptive hash index
自适应hash索引可以让 Innodb 表现得更像是一个基于内存的数据库,前提将buffer pool 的工作负载和 buffer pool 具有合适充足内存合理结合,并且不丧失事务特性和可靠性。自适应 hash 索引可以通过变量innodb adaptive hash index开启或者通过启动参数--skip-innodb-adaptive-hash-index关闭。
基于观察到的搜索模式,使用索引的前缀构建hash索引,前缀长度无限制,但可能只有 B 树的部分值会出现在 hash 索引中,它是由那些使用频繁的 page 对象按需进行构建的。如果一个表几乎完全在主存中,hash 索引可以将索引值转换指针,它允许直接搜索任何元素,这样可以加速查询速度。Innodb 有一种监控索引搜索的机制,如果Innodb发现某些查询可以受益于构建的hash 索引,那它就会自动这么做。
在某些场景下,hash 索引的加速要大大胜过监控搜索和维护 hash 索引结构的额外工作。在繁重的负载下(比如多并发连接),hash 索引可能成为争抢的资源。像like和%匹配符往往不能受益于 hash 索引,对于不能受益于自适应 hash 索引的负载,可以将其关闭以避免不必要的性能开销,因为很难预先预测自适应哈希索引特性是否适合于特定的系统和工作负载,所以考虑在启用和禁用它的情况下运行基准测试。MySQL5.6中的体系结构更改使禁用自适应哈希索引功能比在早期版本中更合适。
自适应hash索引是被切割开的,每个hash索引绑定了某个特定的区域,每个区域都有独立的锁保护,具体的划分行为是由innodb_adaptive_hash_index_parts变量控制,这个值默认是8,最大可设置为512。
【注】innodb 引擎是具备自适应hash索引特性的,默认自动开启,可以通过SHOW VARIABLES LIKE '%adaptive_hash_index%'查看,当InnoDB 注意到某些索引值被使用的非常频繁时,它会在内存中基于 B-Tree 索引上再建立一个 hash 索引,这样就可以让B-Tree 也具备 hash 索引的特性(如快速的hash查找)。这个建立自适应 hash 索引是自动的、内部行为,用户几乎无法控制,只能进行上述基础的开关和区域锁的控制。hash 索引可以创建在一些特别长的字段上(如url),这样可以使用直接使用 hash 值进行快速查找。
3.1.4 log buffer
log buffer 是主要内存区域,它保存着待写入磁盘日志文件的数据。log buffer 的大小由变量 innodb_log_buffer_size 设定,默认为16MB,log buffer 中的内容会定期刷入磁盘中,一个大的 log buffer 可以使得一个大事务在提交前不需要往磁盘写 redo log,因此如果有更新、插入、删除多条记录的大事务,增大 log buffer 的大小可以节省磁盘I/O。
3.2 InnoDB 的磁盘结构
前面的内容基本都是InnoDB的内存结构,InnoDB的磁盘组成结构组要包含下面的几个部分:
- 表 Table;
- 索引 Index;
- 表空间 Tablespace;
- 双写缓冲 Doublewrite Buffer:双写缓冲是一块 InnoDB 将数据从 Buffer pool 中刷入磁盘前的存储区,只有数据在刷入 DW Buffer 后,InnoDB 才会将将数据正式写入磁盘的数据文件中。如果由于某种原因 MySQL 线程在写 Page 退出,那它可以在故障恢复时使用 DW Buffer 中副本数据进行数据恢复。虽然这种机制数据需要写2份,但它不会消耗2倍I/O资源,而是以一份完整的数据块(可能很大)调用操作系统的
fsync()写入。默认双写开启,可通过变量innodb_doublewrite控制(0-disable,1-able)。 - Redo Log:重做日志是一种基于磁盘的数据结构,用于在崩溃恢复期间更正由不完整事务写入的数据,正常操作时,redo log 会将那些会更改数据的请求(即写请求)或底层的api调用进行编码。但若更改数据的请求可能由于某些原因没有完成完整的数据文件的修改,那此时在MySQL服务重启初始化时,在正式接受处理外部连接前将会重放 Redo log 日志。Redo log 在磁盘上主要体现在文件
ib_logfile0和ib_logfile1上。MySQL 写 redo log 时是以循环写的方式进行,redo log 中的数据按照受影响的记录进行编码;这些数据统称为重做,redo log 的过程是由一个不断增长的 LSN 值表示。和其他ACID的数据库引擎一样,它会在事务提交前刷入 redo log,InnoDB 使用组合提交(group commit)的设计来将多个 flush 请求组合批量提交,这样可以避免每次commit都flush一下请求。使用组提交,InnoDB只需向日志文件发出一次写入操作,即可对大约同时提交的多个用户事务执行提交操作,从而显着提高了吞吐量。 - Undo Log:undo log 中记录了一个独立的读写事务撤销日志的集合,一个 undo log 记录了怎样撤销最近的事务对聚簇索引的修改。若另一个事务需要查看原始数据作为一致性读的一部分操作,那此时未更改的数据将会从 undo log 中取出。Undo log 存在于 undo log segment 中,而 undo log segment 又存在于 rollback segment 中,rollback segment 存在于系统表空间中(system tablespace)和 undo tablespace 中以及 temporary tablespace中。rollback segment 支持的事务数量取决于 rollback segment 中 undo 插槽的数量以及每个事务所需的 undo log 的数量。其中 rollback segment 中undo 插槽的数量主要是由InnoDB page size 的大小决定的,通常就是 pageSize/16(如PageSize为4K,那undo 插槽为4*1024/16)。一个事务最多分配4种类型的 undo logs:用户自定义表的
INSERT操作、用户自定义表的UPDATE和DELETE操作、用户自定义临时表的INSERT操作、用户自定义临时表的UPDATE和DELETE操作。


8378

被折叠的 条评论
为什么被折叠?



