暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

InnoDB的底层存储:Page Structure和B+Tree

西门阿浪 2021-06-20
241


吾日三省吾身:高否?帅否?富否?

阿浪:否!否!否!


o( ̄ヘ ̄o#)

所谓人丑就要多读书,阿浪一直对MySQL数据存储的底层结构比较模糊,经过几天几夜的奋发读书(摸鱼),终于让我拨开了萦绕心头的困惑。

存储引擎

 我们都知道MySQL拥有多种存储引擎。MEMORY、MRG_MYISAM、 CSV、FEDERATED、PERFORMANCE_SCHEMA、MyISAMInnoDB、BLACKHOLE、ARCHIVE等,其中最常用的就是MyISAM和InnoDB。

MyISAM引擎

在MySQL 5.5之前的版本,MyISAM是默认的存储引擎。读性能良好,拥有较高的查询速度。

不支持行级锁,只支持表级锁。因为MyISAM会一次性获得SQL所需的全部锁 (这句话包含的信息很多),所以不会出现死锁,这少了很多麻烦事。当MyISAM在执行select/insert/update/delete语句时,会自动给涉及的表加读锁(共享),在执行对表做结构变更操作时,会加写锁(独占)。

当写锁和读锁同时被申请时,优先获得写锁。读锁之间不互斥,读写锁之间、写锁之间是互斥的,通俗的讲,读操作不会阻塞其他线程对同一表的读请求,但会阻塞对同一表的写请求,而写操作会全部阻塞。由于写锁可能会一直阻塞读锁,所以这正是表级锁发生锁冲突概率最高的原因。

本篇文章主要介绍InnoDB的存储,就不对其它知识点做过多拓展。

总结: 不支持行级锁、不支持事物、支持表级锁、读性能较高。

InnoDB引擎

从MySQL 5.5 版本开始,官方选择InnoDB作为MySQL默认的存储引擎。既支持行级锁,也支持表级锁,但默认情况下是采用行级锁。当InnoDB在执行select语句时,不会加任何锁,在执行insert/update/delete操作时,会自动给涉及数据行加排它锁。

InnoDB支持外键,包含外键的InnoDB表转为MyISAM会失败。InnoDB最大的不同就是支持事物,所有的存储引擎中只有InnoDB是事务性存储引擎,对每一条SQL语言都默认封装成事务自动提交。

而且,InnoDB支持MVCC。目前,随着行业发展项目数据的吞吐量越来越大,支持MVCC对于高并发是一个很大的优势。

总结: 支持行级锁、支持表级锁、支持事物。

  更直观的比较:

 综上所述,两者各有各的优势,如何选择应该看自己项目的场景。如果表中绝大多数都只是读查询,可以考虑MyISAM,如果既有读也有写,使用InnoDB效果更好。

页(Page Structure)

 大学学习操作系统的时候,我们知道 "页" 是操作系统从磁盘取数据到内存的基本单位,默认为4KB。虽然部分处理器会使用 8KB、16KB 或者 64KB 作为默认的页面大小,但是4KB的页面仍然是操作系统默认内存页配置的主流。

CPU在做指令操作时,从目标地址取数据到内存需要一次IO操作,多次操作数据就要相应发生多次IO。若每次取的数据都很小(远小于4KB),为减少IO操作每次直接取一页(4KB)。

InnoDB中的“页”

在MySQL中,select指令根据where字段查找数据时,表中每行数据都要被读到内存中依次与之比较,有多少条数据就要发生多少次IO操作。这样资源消耗太大,于是InnoDB借鉴了操作系统对内存的管理,引入了“Page Structure” 来减少IO操作。

InnoDB采用”页“的形式存储用户数据,有时也被称为“块”。MySQL默认的非压缩数据页为16KB,0~16KB偏移量即为0号数据页,16KB-32KB的为1号数据页,依次类推。

从MySQL官网可以了解到,InnoDB的页结构包含以下内容:

因为Page的内部结构很复杂,阿浪只能带着大家从宏观上浅层了解,感兴趣的同学可以对着官方文档深入研究:https://dev.mysql.com/doc/internals/en/innodb-page-structure.html

  • Fil Header

    fil header用来描述页信息。记录了页的空间ID,区分了记录在同一文件内所属不同表空间的数据页,并且提供指针连接各个数据页。

    其中最重要的是 fil_page_prevfil_page_next 两个指针,当表数据达到一定规模时,就会生成大量的数据页,这时候通过指针以双向链表的形式维护数据页的连接性和有序性。

  • Page Header

    用于记录页内的状态信息。分配新页的信息、页内第一条数据地址等。

  • User Records

    表内有多少条数据,UserRecords就要存储多少条记录。若是主键,存储完整的表数据,并根据主键升序排列来提升检索效率。若是非主键索引,则根据索引字段排序。

  • Page Directory

    UserRecords可能保存一个很长的单链表,因为长链表不支持随机访问,检索时间随着数据规模的增长而增长。所以PageDirectory以组划分链表记录用户数据的相对位置,默认以6条数据为一组。

    当查询数据时,直接与Page Directory内的数据比较快速锁定数据范围,然后进入UserRecodes获取完整数据。

 画图看更直观(简图):

物理划分上的“区” “段”

区(extent):由连续的64个16KB的Page组成的大小为1M的空间。区是实际的1M物理空间。当碎片区用完之后,就会申请1M的连续物理空间。

段(segment):由一个或多个区组成,不要求是地址连续的区。当我们创建数据表、索引的时候,就会相应创建对应的段,比如创建一张表时会创建一个表段,创建一个索引时会创建一个索引段。

我从网上找到一张图,很形象的展示了区在表空间内的结构。

B-Tree与B+Tree

传统的B-Tree

有些人会读成“B减树”,虽然只是个名称,但出于规范化考虑,还是推荐把“-”看成连接符,读作“B树”。

说到B树可能日常中不常用,但二叉查找树(Binary Search Tree) 大家应该很熟悉。对于它的每个节点有这么个特性,若左子树不空,则左子树上所有结点的值均小于它的根结点的值。若右子树不空,则右子树上所有结点的值均大于它的根结点的值。

在二叉查找树的基础上又产生了平衡二叉查找树(Self-Balancing Binary Search Tree),增加了 左子树与右子树的高度之差的绝对值不超过1的特性,大名鼎鼎有的AVL树和红黑树。下面是一颗AVL树:

B树就是在平衡二叉树基础上实现的多路平衡查找树,继承了二叉平衡树的所有特征,又由于自身是多叉树,所以是个“小胖墩”。下面是一颗最小度数为5的B树:

我们很明显的看出来B树比AVL树的高度要小,这会带来什么好处呢?

 如果数据都放在磁盘里,与内存访问时间相比,磁盘访问时间非常长。大多数树操作需要 O(h) 次磁盘访问,其中h是树的高度。由于B树的高度较低,因此与二叉平衡树(如 AVL 树、红黑树等)相比,大多数操作的总磁盘访问量显着减少。

了解B树后,我们知道了从根节点到叶子节点的连接特性,但这样会损失叶子节点也可以相互指向带来的效率。InnoDB允许在叶与叶之间相互指向,所以InnoDB内被称为B+树。

InnoDB中的B+Tree

 其实B+树仅仅只是B树的一个变种,而InnoDB中的B+树又在此基础上做了一个小改进。下面是最大度数为4的B+树:

与上面的B树对比,最明显的是叶子结点通过指针相互连接,只看叶子结点的话就是一条单链表。为何说InnoDB里的B+树是传统B+树的改良版呢?因为InnoDB里的B+树的叶子结点不只有后继指针,还有多出来的前驱指针,叶子结点构成了一条双向链表。


文章转载自西门阿浪,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论