引言
说到存储引擎,我们在上文说过mysql的存储引擎结构是可插拔的,目前主要有InnoDB、Myism、Memory(不常用)这些,而我们最常用的是InnoDB,我们会对比一下这几个存储引擎,并重点讲一下InnoDB。
1. 为啥我们常选用InnoDB
我们对主要存储引擎进行一下对比:
类型 | InnoDB | Myisam | Memory |
查询速度 | 中 | 高 | 高 |
支持事务 | Y | N | N |
数据结构 | B+ | B+ | HASH |
锁机制 | 行锁 | 表锁 | 表锁 |
数据压缩 | N | Y | Y |
支持外键 | Y | N | N |
从上图我们可以看到,单纯说查询数据,InnoDB不如其他的,但我们日常对数据库的使用,多为读写频繁的场景,表锁对频繁写数据的场景是不能接受的,而数据操作的完整性是日常工作中又是常见要求,所以事务很重要,基于这两点导致我们基本上都会选用InnoDB,Mysql也把默认存储引擎设为InnoDB(Mysql5.5版本及之后)。
2. InnoDB存储引擎是什么样子?
2.1 InnoDB 体系架构
InnoDB 主要包括了:内存池、后台线程以及存储文件。
内存池是由多个内存块组成的,主要包括缓存磁盘数据、undolog、redolog 缓冲等;
后台线程包括Master Thread、IO Thread 以及 Purge Thread 等基础的执行线程;
存储结构文件一般包括表结构文件(.frm)、共享表空间文件(ibdata1)、独占表空间文件(ibd)以及日志文件(redolog、undolog 文件等)等。
2.2. 内存池
为了减少频繁操作磁盘带来一定的性能瓶颈,提高整个数据库的读写性能Mysql中设计了内存缓冲池。
客户端读取数据时,如果数据存在于缓冲池中,客户端就会直接读取缓冲池中的数据,否则再去磁盘中读取;对于数据库中的修改数据,首先是修改在缓冲池中的数据,然后再通过 Master Thread 线程刷新到磁盘上。
理论上来说,缓冲池的内存越大越好。缓冲池中不仅缓存索引页和数据页,还包括了 undolog 页,插入缓存、自适应哈希索引以及 InnoDB 地锁信息等等。
InnoDB允许多个缓冲池实例,从而减少数据库内部资源的竞争,增强数据库的并发处理能力,
InnoDB存储引擎会先将redolog日志信息放入到缓冲区中,然后再刷新到redolog日志文件中。
8.0版本之后查询缓存被取消,其实也挺好理解,查询缓存被重复使用的概率较低,且占内存,多线程请求的情况下都先查询缓存,命中率不高反而影响效率。
2.3. 后台线程
Master Thread 主要负责将缓冲池中的数据异步刷新到磁盘中,除此之外还包括插入缓存、undo 页的回收等,IO Thread 是负责读写 IO 的线程,而 Purge Thread 主要用于回收事务已经提交了的 undolog,Pager Cleaner Thread 是新引入的一个用于协助 Master Thread 刷新脏页到磁盘的线程,它可以减轻 Master Thread 的工作压力,减少阻塞。
2.4. 存储文件
在 MySQL中建立一张表都会生成一个.frm 文件,该文件是用来保存每个表的元数据信息的,主要包含表结构定义。
在 InnoDB 中,存储数据都是按表空间进行存放的,默认为共享表空间,存储的文件即为共享表空间文件(ibdata)
3. InnoDB的索引数据结构为啥是B+树?
我们在上面表格中展示InnoDB的默认数据结构是B+树,这时可能就有小伙伴要问了,为啥选B+树,是否有其他选择,我们下面就这个问题进行简化版推导分析:
首先我们有哪些选择,考虑到要数据需要持久化到磁盘,支持快速存储和查询,链表、栈之类的不太符合,优先考虑树和Hash,Hash无法做范围匹配,所以我们可以从树里面看,
树有哪些选择:二叉树、红黑树、AVL、B树、B+树等;如何取舍?重点分析它们对检索效率的影响,这个时候我们需要清楚一点:索引是存储在磁盘上的,影响查询的核心因素之一就是磁盘IO。
二叉树
红黑树
树分为二叉树和多叉树,每个层级的一次检索都相当于一次IO,而随着数据量增大,相比多叉树,二叉树的的层级将不可控,IO次数增多,效率也就没法保证,所以重点考虑、B树、B+树这种多叉树。
3.1 接下来我们就重点讨论B树和B+树:
B树结构如下:
B树是多叉平衡树,每个阶段可以存储多个数据,而且每个节点不止两个节点,可以有多个节点,B数的每个节点都可以存放着索引和数据,也就是说并非只有叶子节点存放数据,数据遍布整个数结构,搜索可能在非叶子节点结束,最好的情况是O(1)。
B+树,结构如下:
B+树结构上和B树类似,也是多叉树,区别在于:
1.数据只会存储在叶子节点,所以最好的查询效率O(2),比B树多一次。
2.叶子节点中有下个叶子节点的指针,整个叶子节点形成一个双向链表。
对比我们发现,B树和B+树最大的区别在于上级节点是否包含数据,既数据是否全部存储在叶子节点,虽然最优情况下的查询效率B树还好于B+树,但从mysql的选择上来看B+貌似好一些
为啥数据存储的位置问题会带来这种影响
这里我们就要插一个小知识点了,计算机局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用;
根据这一原理为减少IO,Mysql设计了按页预读的机制,即每次读取会顺序读取多页数据放到内存(一个页的大小,通常是16K),也就说我们的索引数据也是按页存储按页读取的;
我们来假设一个场景(以下都是粗略计算,理解这个意思就行):
现在有一批数据,id(主键)大小都为1个字节,数量大小为1kb,
1. 用B树存储,一页差不多能存15个,三层的B树约15*15*15,约3000多数据,
2. 用B+树的话,索引页能存1000多,按1000算吧,叶子节点也就是数据页能存16个,三层的B+树能存1000*1000*16,约1600万数据;
那也就是说在数据量大一些、数据大小未知的情况下,按页存储读取,B树的层级也是不可控的,IO消耗不可控,而B+树则好的多,而且其叶子节点链表的设计也在顺序读取时比较省力。
4.InnoDB为啥能支持事务而Myisam没有?
在对比的时候我们说了InnoDB的一大优势是支持事务,支持行锁,其实他俩有点相辅相成的,因为有行锁所以方便支持事务,而Myisam是不支持这些,虽然他俩的索引数据结构都是B+树,
为什么?
1. 不实现:这就是历史原因了,场景也不同,Myisam的更适用读,速度快,锁会影响效率。
2. 不方便实现:在这我们需要说一下另外一个概念:聚簇索引和非聚簇索引。
聚簇索引:将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据
非聚簇索引:又称辅助索引,将数据与索引分开存储,索引结构的叶子节点指向了数据对应的位置。
我们常说InnoDB使用的是聚簇索引,其实是不准确的,它其实既有聚簇索引又有非聚簇索引,及默认的主键索引(可设置)是聚簇索引,其他的是以叶子节点存储主键信息的非聚簇索引。
Myisam使用的是非聚簇索引,叶子节点不存储具体数据,存储的事数据的存储位置。聚簇索引是数据物理有序的,非聚簇索引是逻辑有序,物理无序的,Myisam实现数据锁定代价会大一些。