简单了解 InnoDB 底层原理
存储引擎
很多文章都是直接开始介绍有哪些存储引擎,并没有去介绍存储引擎本身。那么究竟什么是存储引擎?不知道大家有没有想过,MySQL 是如何存储我们丢进去的数据的?
其实存储引擎也很简单,我认为就是一种存储解决方案,实现了新增数据、更新数据和建立索引等等功能。
有哪些已有的存储引擎可以让我们选择呢?
InnoDB、MyISAM、Memory、CSV、Archive、Blackhole、Merge、Federated、Example
种类很多,但是常用的存储引擎目前就只有 InnoDB 和 MyISAM,我也会着重来介绍这两种存储引擎。
InnoDB 是目前使用最广的 MySQL 存储引擎,MySQL 从 5.5 版本开始 InnoDB 就已经是默认的存储引擎了。那你知道为什么 InnoDB 被广泛的使用呢?先把这个问题放一放,我们先来了解一下 InnoDB 存储引擎的底层原理。
InnoDB 的内存架构主要分为三大块,缓冲池(Buffer Pool)、重做缓冲池(Redo Log Buffer)和额外内存池
缓冲池
InnoDB 为了做数据的持久化,会将数据存储到磁盘上。但是面对大量的请求时,CPU 的处理速度和磁盘的 IO 速度之间差距太大,为了提高整体的效率, InnoDB 引入了缓冲池。
当有请求来查询数据时,如果缓存池中没有,就会去磁盘中查找,将匹配到的数据放入缓存池中。同样的,如果有请求来修改数据,MySQL 并不会直接去修改磁盘,而是会修改已经在缓冲池的页中的数据,然后再将数据刷回磁盘,这就是缓冲池的作用,加速读,加速写,减少与磁盘的 IO 交互。
缓冲池说白了就是把磁盘中的数据丢到内存,那既然是内存就会存在没有内存空间可以分配的情况。所以缓冲池采用了 LRU 算法,在缓冲池中没有空闲的页时,来进行页的淘汰。但是采用这种算法会带来一个问题叫做缓冲池污染。
当你在进行批量扫描甚至全表扫描时,可能会将缓冲池中的热点页全部替换出去。这样以来可能会导致 MySQL 的性能断崖式下降。所以 InnoDB 对 LRU 做了一些优化,规避了这个问题。
MySQL 采用日志先行,在真正写数据之前,会首先记录一个日志,叫 Redo Log,会定期的使用 CheckPoint 技术将新的 Redo Log 刷入磁盘,这个后面会讲。
除了数据之外,里面还存储了索引页、Undo 页、插入缓冲、自适应哈希索引、InnoDB 锁信息和数据字典。下面选几个比较重要的来简单聊一聊。
插入缓冲
插入缓冲针对的操作是更新或者插入,我们考虑最坏的情况,那就是需要更新的数据都不在缓冲池中。那么此时会有下面两种方案。
来一条数据就直接写入磁盘
等数据达到某个阈值(例如 50 条)才批量的写入磁盘
很明显,第二种方案要好一点,减少了与磁盘 IO 的交互。
两次写
鉴于都聊到了插入缓冲,我就不得不需要提一嘴两次写,因为我认为这两个 InnoDB 的特性是相辅相成的。
插入缓冲提高了 MySQL 的性能,而两次写则在此基础上提高了数据的可靠性。我们知道,当数据还在缓冲池中的时候,当机器宕机了,发生了写失效,有 Redo Log 来进行恢复。但是如果是在从缓冲池中将数据刷回磁盘的时候宕机了呢?
这种情况叫做部分写失效,此时重做日志就无法解决问题。
在刷脏页时,并不是直接刷入磁盘,而是 copy 到内存中的 Doublewrite Buffer 中,然后再拷贝至磁盘共享表空间(你可以就理解为磁盘)中,每次写入 1M,等 copy 完成后,再将 Doublewrite Buffer 中的页写入磁盘文件。
有了两次写机制,即使在刷脏页时宕机了,在实例恢复的时候也可以从共享表空间中找到 Doublewrite Buffer 的页副本,直接将其覆盖原来的数据页即可。
自适应哈希索引
自适应索引就跟 JVM 在运行过程中,会动态的把某些热点代码编译成 Machine Code 一样,InnoDB 会监控对所有索引的查询,对热点访问的页建立哈希索引,以此来提升访问速度。
你可能多次看到了一个关键字页,接下来那我们就来聊一下页是什么?
页
页,是 InnoDB 中数据管理的最小单位。当我们查询数据时,其是以页为单位,将磁盘中的数据加载到缓冲池中的。同理,更新数据也是以页为单位,将我们对数据的修改刷回磁盘。每页的默认大小为 16k,每页中包含了若干行的数据,页的结构如下图所示。
不用太纠结每个区是干嘛的,我们只需要知道这样设计的好处在哪儿。每一页的数据,可以通过 FileHeader 中的上一下和下一页的数据,页与页之间可以形成双向链表。因为在实际的物理存储上,数据并不是连续存储的。你可以把他理解成 G1 的 Region 在内存中的分布。
而一页中所包含的行数据,行与行之间则形成了单向链表。我们存入的行数据最终会到 User Records 中,当然最初 User Records 并不占据任何存储空间。随着我们存入的数据越来越多,User Records 会越来越大,Free Space 的空间会越来越小,直到被占用完,就会申请新的数据页。
User Records 中的数据,是按照主键 id 来进行排序的,当我们按照主键来进行查找时,会沿着这个单向链表一直往后找,
重做日志缓冲
上面聊过,InnoDB 中缓冲池中的页数据更新会先于磁盘数据更新的,InnoDB 也会采用日志先行(Write Ahead Log)策略来刷新数据,什么意思呢?当事务开始时,会先记录 Redo Log 到 Redo Log Buffer 中,然后再更新缓冲池页数据。
Redo Log Buffer 中的数据会按照一定的频率写到重做日志中去。被更改过的页就会被标记成脏页,InnoDB 会根据 CheckPoint 机制来将脏页刷到磁盘。
日志
上面提到了 Redo log,这一小节就专门来讲一讲日志,日志分为如下两个维度。
MySQL 层面
InnoDB 层面
MySQL 日志
MySQL 的日志可以分为错误日志、二进制文件、查询日志和满查询日志。
错误日志 很好理解,就是服务运行过程中发生的严重错误日志。当我们的数据库无法启动时,就可以来这里看看具体不能启动的原因是什么
二进制文件 它有另外一个名字你应该熟悉,叫 Binlog,其记录了对数据库所有的更改。
查询日志 记录了来自客户端的所有语句
慢查询日志 这里记录了所有响应时间超过阈值的 SQL 语句,这个阈值我们可以自己设置,参数为
long_query_time
,其默认值为 10s,且默认是关闭的状态,需要手动的打开。
InnoDB 日志
InnoDB 日志就只有两种,Redo Log 和 Undo Log,
Redo Log 重做日志,用于记录事务操作的变化,且记录的是修改之后的值。不管事务是否提交都会记录下来。例如在更新数据时,会先将更新的记录写到 Redo Log 中,再更新缓存中页中的数据。然后按照设置的更新策略,将内存中的数据刷回磁盘。
Undo Log 记录的是记录的事务开始之前的一个版本,可用于事务失败之后发生的回滚。
Redo Log 记录的是具体某个数据页上的修改,只能在当前 Server 使用,而 Binlog 可以理解为可以给其他类型的存储引擎使用。这也是 Binlog 的一个重要作用,那就是主从复制,另外一个作用是数据恢复。
上面提到过,Binlog 中记录了所有对数据库的修改,其记录日志有三种格式。分别是 Statement、Row 和 MixedLevel。
Statement 记录所有会修改数据的 SQL,其只会记录 SQL,并不需要记录下这个 SQL 影响的所有行,减少了日志量,提高了性能。但是由于只是记录执行语句,不能保证在 Slave 节点上能够正确执行,所以还需要额外的记录一些上下文信息
Row 只保存被修改的记录,与 Statement 只记录执行 SQL 来比较,Row 会产生大量的日志。但是 Row 不用记录上下文信息了,只需要关注被改成啥样就行。
MixedLevel 就是 Statement 和 Row 混合使用。
具体使用哪种日志,需要根据实际情况来决定。例如一条 UPDATE 语句更新了很多的数据,采用 Statement 会更加节省空间,但是相对的,Row 会更加的可靠。
InnoDB 和 MyISAM 的区别
由于 MyISAM 并不常用,我也不打算去深究其底层的一些原理和实现。我们在这里简单的对比一下这两个存储引擎的区别就好。我们分点来一点点描述。
事务 InnoDB 支持事务、回滚、事务安全和奔溃恢复。而 MyISAM 不支持,但查询的速度要比 InnoDB 更快
主键 InnoDB 规定,如果没有设置主键,就自动的生成一个 6 字节的主键,而 MyISAM 允许没有任何索引和主键的存在,索引就是行的地址
外键 InnoDB 支持外键,而 MyISAM 不支持
表锁 InnoDB 支持行锁和表锁,而 MyISAM 只支持表锁
全文索引 InnoDB 不支持全文索引,但是可以用插件来实现相应的功能,而 MyISAM 是本身就支持全本索引
行数 InnoDB 获取行数时,需要扫全表。而 MyISAM 保存了当前表的总行数,直接读取即可。
所以,简单总结一下,MyISAM 只适用于查询大于更新的场景,如果你的系统查询的情况占绝大多数(例如报表系统)就可以使用 MyISAM 来存储,除此之外,都建议使用 InnoDB。
End
由于时间的原因,本文只是简单的聊了聊 InnoDB 的整体架构,并没有很深入的去聊某些点。例如 InnoDB 是如何改进来解决缓冲池污染的,其算法具体是什么,checkpoint 是如何工作的等等,只是做一个简单的了解,之后如果有时间的话再细聊。
好了以上就是本篇博客的全部内容了,欢迎微信搜索关注【SH 的全栈笔记】,回复【队列】获取 MQ 学习资料,包含基础概念解析和 RocketMQ 详细的源码解析,持续更新中。
如果你觉得这篇文章对你有帮助,还麻烦点个赞,关个注,分个享,留个言。
版权声明: 本文为 InfoQ 作者【SH的全栈笔记】的原创文章。
原文链接:【http://xie.infoq.cn/article/bba4a0d3893f81616364e754b】。文章转载请联系作者。
评论