前言
简单介绍一下页存储的关系。
正文
在前文中,我们已经知道了页存储的内怎么去查询的数据的,也就两点。
-
记录根据主键(索引)按照顺序链式存储
-
有一个page directory,里面有槽,可以快速定位到槽,然后就可以从链式存储的某个点进行分组查询
理论上这样在一页内查询还是非常ok的。
那么有一个问题,就是一页我们知道了只有16k,不可能全部的数据放在16k上。
然后我们有知道了,这些页呢,在物理层上是双链表。
嗯,这里我们知道了,如果需要查任何一个数据,我们其实是可以查到的,因为不存在孤岛的问题。
现在就是另外一个问题,如何快速定位到数据属于哪一个页呢?
这似乎是一个值得思考的问题。
再简单点说,我们有两个页:
咦?怎么分配的页号是 28 呀,不应该是 11 么?再次强调一遍,新分配的数据页编号可能并不是连续的,也
就是说我们使用的这些页在存储空间里可能并不挨着。它们只是通过维护着上一个页和下一个页的编号而建
立了链表关系。另外, 页10 中用户记录最大的主键值是 5 ,而 页28 中有一条记录的主键值是 4 ,因为 5
4 ,所以这就不符合下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值的要求,所
以在插入主键值为 4 的记录的时候需要伴随着一次记录移动,也就是把主键值为 5 的记录移动到 页28 中,
然后再把主键值为 4 的记录插入到 页10 中,这个过程的示意图如下:
这似乎就对了,所以这也是为什么我们说的页分裂问题。
如果不按照顺序插入,那么总得这里移动哪里移动啥的,极端的情况会发生连锁反应。
这不是我们要解决的问题,想必开发者应该有这个想法了。
然后我们就有了很多页。
因为这些 16KB 的页在物理存储上可能并不挨着,所以如果想从这么多页中根据主键值快速定位某些记录所
在的页,我们需要给它们做个目录,每个页对应一个目录项,每个目录项包括下边两个部分:
页的用户记录中最小的主键值,我们用 key 来表示。
页号,我们用 page_no 表示。
然后我们做了一个目录。
神奇的现象发生了:
如果我们抽象一下会怎么样呢?
有没有可能(key:1, page_no) 就是一条记录呢? 且1,5,12,209 是顺序的,那不就完全符合我们的页的结构了。
有趣。
那么这样的记录叫做目录记录。
record_type 属性,它的各个取值代表的意思如下:
0 :普通的用户记录
1 :目录项记录
2 :最小记录
3 :最大记录
整体来说就行下面这样:
那么现在查询数据,那么就是页30通过二分法查到是哪一条记录。
比例比如说查8吧。
因为8在30页的,5-12之间,因为目录页里面记录的是最小值,那么是左闭右开,所以定位到是(5,28)这条记录。
那么找到28页,然后就去二分法找到记录啥的。
虽然说 目录项记录 中只存储主键值和对应的页号,比用户记录需要的存储空间小多了,但是不论怎么说一个页
只有 16KB 大小,能存放的 目录项记录 也是有限的,那如果表中的数据太多,以至于一个数据页不足以存放所有
的 目录项记录 ,该咋办呢?
来,给目录页加上:
那么现在查询的就需要下面几个步骤了:
- 确定 目录项记录 页
- 通过 目录项记录 页确定用户记录真实所在的页。
- 在真实存储用户记录的页中定位到具体的记录。
那么问题来了,在这个查询步骤的第1步中我们需要定位存储 目录项记录 的页,但是这些页在存储空间中也可能
不挨着,如果我们表中的数据非常多则会产生很多存储 目录项记录 的页,那我们怎么根据主键值快速定位一个
存储 目录项记录 的页呢?其实也简单,为这些存储 目录项记录 的页再生成一个更高级的目录,就像是一个多级
目录一样,大目录里嵌套小目录,小目录里才是实际的数据
这玩意儿像不像一个倒过来的 树 呀,上头是树根,下头是树叶!其实这是一种组织数据的形式,或者说是一种
数据结构,它的名称是 B+ 树。
从图中可以看出来,我们的实际用户记录其实都存放在B+树的最底层的节点
上,这些节点也被称为 叶子节点 或 叶节点 ,其余用来存放 目录项 的节点称为 非叶子节点 或者 内节点 ,其
中 B+ 树最上边的那个节点也称为根节点。
我们每次查询的时候就是从这个根节点开始查。
那么问题来了,mysql 怎么知道根节点在哪呢?
那么肯定有记录的,至于在哪后面再说。
- 每当为某个表创建一个 B+ 树索引(聚簇索引不是人为创建的,默认就有)的时候,都会为这个索引创建一
个 根节点 页面。最开始表中没有数据的时候,每个 B+ 树索引对应的 根节点 中既没有用户记录,也没有目
录项记录。 - 随后向表中插入用户记录时,先把用户记录存储到这个 根节点 中。
- 当 根节点 中的可用空间用完时继续插入记录,此时会将 根节点 中的所有记录复制到一个新分配的页,比
如 页a 中,然后对这个新页进行 页分裂 的操作,得到另一个新页,比如 页b 。这时新插入的记录根据键值
(也就是聚簇索引中的主键值,二级索引中对应的索引列的值)的大小就会被分配到 页a 或者 页b 中,而
根节点 便升级为存储目录项记录的页。
然后还有一个问题,那就是二级索引的问题。
我们知道二级索引的记录里面存在是 索引列+主键对吧。
然后二级索引的目录是怎么样的呢?
是存这索引列加页号吗?
当然不是,因为其实二级索引记录里面主键是啥? 是索引列+主键,而不单单是主键。
所以应该是这样的:
结
先这么着吧,后面再补充。