从前面的文章,我们已经了解到了目前MySQL最常用的存储引擎就是InnoDB,但是InnoDB具体是什么样结构,是如何将数据保存的我们并没有聊到,本期就简单聊一聊InnoDB的底层结构与实现
我们先看下MySQL官网给出的结构图,如下:
从上图我们可以很清楚的看到,InnoDB分为两大块:内存结构(in-Memory structures)、磁盘结构(in-Disk structures)
内存结构
内存结构主要包括Buffer Pool、Change Buffer、Adaptive Hash Index和Log Buffer四大组件。接下来我们分别讲下各个组件。
一、Buffer pool(缓冲池)
缓冲池以页为最小单位来存储多行数据,是缓冲表、索引数据、高频数据、缓冲池写磁盘时候一般直接写入系统表空间的双写缓冲区。对数据库数据进行操作时都是先读取到内存缓冲区进行操作后再写入磁盘。
通常来讲Buffer Pool越大,能缓存的数据就更多,更多的操作都会发生在内存,从而达到提升效率的目的。
1.1 页
是InnoDB中数据管理的最小单位。页分为,数据页(BTreeNode),Undo页(undo Log page),系统页(Systempage),事务数据页(Transaction SystemPage)每个数数据页的大小为16kb,每个page使用一个32位int值来表示。
当我们查询数据时,其是以页为单位,将磁盘中的数据加载到缓冲池中的。同理,更新数据也是以页为单位,将我们对数据的修改刷回磁盘。每页的默认大小为16k,每页中包含了若干行的数据,页的结构如下图所示。
图片来源于网络, 侵删
不用太纠结每个区是干嘛的,我们只需要知道这样设计的好处在哪儿。每一页的数据,可以通过FileHeader中的上一页和下一页的数据,页与页之间可以形成双向链表。因为在实际的物理存储上,数据并不是连续存储的。你可以把它理解成G1的Region在内存中的分布。
而一页中所包含的行数据,行与行之间则形成了单向链表。我们存入的行数据最终会到User Records中,当然最初User Records并不占据任何存储空间。随着我们存入的数据越来越多,User Records会越来越大,Free Space的空间会越来越小,直到被占用完,就会申请新的数据页。
User Records中的数据,是按照主键id来进行排序的,当我们按照主键来进行查找时,会沿着这个单向链表一直往后找。如下图:
1.2、 Adaptive Hash Index::自适应哈希索引
是配合Buffer Pool工作的一个功能。自适应哈希索引使得MySQL的性能更加接近于内存服务器。
自适应哈希索引是根据索引Key的前缀来构建的,InnoDB 有自己的监控索引的机制,当其检测到为当前某个索引页建立哈希索引能够提升效率时,就会创建对应的哈希索引。如果某张表数据量很少,其数据全部都在Buffer Pool中,那么此时自适应哈希索引就会变成我们所熟悉的指针这样一个角色。
如果要启用自适应哈希索引,可以通过更改配置innodb_adaptive_hash_index来开启。如果不想启用,也可以在启动的时候,通过命令行参数--skip-innodb-adaptive-hash-index来关闭。
当然,创建、维护自适应哈希索引是会带来一定的开销的,但是比起其带来的性能上的提升,这点开销可以直接忽略不计。但是,是否要开启自适应哈希索引还是需要看具体的业务情况的,例如当我们的业务特征是有大量的并发Join查询,此时访问自适应哈希索引被产生竞争。并且如果业务还使用了LIKE或者%等通配符,根本就不会用到哈希索引,那么此时自适应哈希索引反而变成了系统的负担。
所以,为了尽可能的减少并发情况下带来的竞争,InnoDB对自适应哈希索引进行了分区,每个索引都被绑定到了一个特定的分区,而每个分区都由单独的锁进行保护。其实通俗点理解,就是降低了锁的粒度。分区的数量我们可以通过配置innodb_adaptive_hash_index_parts来改变,其可配置的区间范围为[8, 512]。
1.3、Change Buffer(变更缓冲区、有人又叫写缓冲区)
用来存储二级索引变更的数据结构(缓冲池没有该内容),可能由insert,update,delete等dml操作造成的。后面会由读操作将页面加载到缓冲池合并。缓冲池周期性地将更改的索引页写进磁盘。在内存中,变更缓冲区属于缓冲池一部分,磁盘上,缓冲区属于系统表空间一部分,当数据库服务宕机时,索引变更存储在内存变更缓冲区。
换句话说当我们更新了非聚簇索引(二级索引)的数据时,此时应该是直接将其在Buffer Pool中的对应数据更新了即可,但是不凑巧的是,当前二级索引不在 Buffer Pool 中,此时将其从磁盘拉取到 Buffer Pool 中的话,并不是最优的解,因为该二级索引可能之后根本就不会被用到,那么刚刚进行的昂贵的磁盘I/O操作就白费了。
所以,我们需要这么一个地方,来暂存对这些二级索引所做的改动。当被缓存的二级索引页被其他的请求加载到了Buffer Pool 中之后,就会将 Change Buffer 中缓存的数据合并到 Buffer Pool 中去。
当然,Change Buffer也不是没有缺点。当 Change Buffer 中有很多的数据时,全部合并到Buffer Pool可能会花上几个小时的时间,并且在合并的期间,磁盘的I/O操作会比较频繁,从而导致部分的CPU资源被占用。
change buffer 占用BufferPool空间默认占25%,最大允许占50%,可以根据读写业务量来 进行调整。参数innodb_change_buffer_max_size
二、Log Buffer(日志缓冲区)
用来保存要写入磁盘上log文件(Redo/Undo)的数据。日志缓冲区的内容定期刷新到磁盘log文件中。日志缓冲区满时会自动将其刷新到磁盘,当遇到BLOB 或多行更新的大事务操作时,增加日志缓冲区可以节省磁盘I/O
日志缓冲区大小由变量innodb_log_buffer_size配置,默认大小是16MB
当Log Buffer如果较大,就可以存储更多的Redo Log,这样一来在事务提交之前我们就不需要将Redo Log刷入磁盘,只需要丢到Log Buffer中去即可。因此较大的Log Buffer就可以更好的支持较大的事务运行;同理,如果有事务会大量的更新、插入或者删除行,那么适当的增大Log Buffer的大小,也可以有效的减少部分磁盘I/O操作。
至于Log Buffer中的数据刷入到磁盘的频率,则可以通过参数innodb_flush_log_at_trx_commit来决定。
关于InnoDB的结构这期先说到这里,我们下期接着聊一聊InnoDB的磁盘结构!
…………………………………分割线……………………………
不积跬步,无以至千里;不积小流,无以成江海。
关注我,每天分享一些小知识点。分享自己的小心得,包含但不限于初、中、高级面试题呦!!!
我都墨迹这么半天了 ,你不点关注,不点赞,不收藏,还不转发,你想干啥!!!!
网友评论