跨境物流火爆,运无界完成数千万元战略融资
06-18
上一篇文章分析了InnoDB的行格式,但这里扔了记录头信息。那么让我们开始吧。
请注意,本片需要一定的数据结构和算法基础。如果基础薄弱,请先确保您了解二分查找和链表,然后再简单提及页面结构。
页是InnoDB管理存储空间的基本单位。一个页面的大小一般为16KB。
InnoDB为了不同的目的设计了很多不同类型的页,比如存储表空间头信息的页、存储Insert Buffer信息的页、存储INODE信息的页、存储undo log信息的页等等。但在这里,我们主要讨论的是索引页的简要描述。
虽然只有16kb,但是可以分割成更多的图像。 Image-0image-7记录存储在页面中。
我们的存储记录将以行格式存储在用户记录中。其实UserRecords是来自于Free Space中划分的emm...这其实是一个数据结构的线性表,嗯,就是一个数组。
看来,又废话了,继续一张图直接理解image-3。但这里使用Inoodb是为了更好的管理。
UserRecords还是浪费了很多精力。这得从行格式的记录头信息中揭示出来:我们再处理一下行头信息{#line_header}。
代码语言:sql copy mysql> CREATE TABLE page_demo( -> c1 INT, -> c2 INT, -> c3 VARCHAR(0), -> PRIMARY KEY (c1) -> ) CHARSET=ascii ROW_FORMAT=Compact;Query OK, 0受影响的行数 (0.03 秒) 请注意,在本例中,我们使用 c1 作为主键,因此在行格式内存结构中,c1 取代了 row_id。内存结构图如下image-9然后把旧图拉出来。
Image-9 下面的结构我不会简化,只是在这里使用有用的点。了解了 image-4 的基本结构后,我们来尝试插入几种数据代码语言: sql copy mysql> INSERT INTO page_demo VALUES(1, , 'aaaa'), (2, , 'bbbb' ), (3, , 'cccc'),(4, , 'dddd');Query OK, 4 rows受影响 (0.00 sec)Records: 4 Duplicates: 0 warnings: 0以下是 UserRecords 中的标题信息和实际列数据 表示(是十进制)在这里,但它本质上是二进制的)。
image-9delete_mask 有这个标识符并不奇怪。大概就是 明秀栈道。
我在黑暗城苍查过。执行删除后,delete_mask被删除。
标记一下,但是找不到结果,但是底部会形成一条垃圾链。垃圾链是可重复利用的空间。
如果以后有新的记录,您可以为什么不直接删除它,因为它会覆盖重复使用的空间?删除后重新排列会消耗性能。另外,将delete_mask位设置为1和将删除的记录添加到垃圾列表实际上是两个阶段。
后面讲交易的时候,西索min_reck_mask会被添加到B+树的每一层非叶子节点中的最小记录中。我们插入的4条记录的min_rec_mask值都是0,这意味着它们不是B+树的非叶子节点中最小的。
记录n_owned我们稍后会讲heap_no。简单来说就是当前记录(行)在页面中的位置是2、3、4、5? ? ? 0和1呢?在Innodb的开发过程中,有两条记录(伪记录)被偷偷地插入其中。
一个代表最大记录,另一个代表最小记录。什么?比较大小是正确的,就是这样:对于一条完整的记录,比较记录的大小就是比较主键的大小。
比如我们插入的四行记录的主键值为:1、2、3、4。这意味着这四行记录的大小从小到大递增。
当然,对于只存储一条记录的部分列的情况,后面会讨论细所。那么这两个臭小子到底长啥样呢? 跟随 :mouse::mouse: 并看一下 image-0 作为一个单词,wtf?是的,是一个单词,但是它没有放在UserRecords中,而是放在一个叫Infimum + Supremum的地方 image-4 从图中我们可以看到最小记录和最大记录的heap_no值都是0和 1 分别。
,也就是说,它们位于前面。 record_type表示当前记录的类型。
有四种类型的记录。 0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录。
从图中我们还可以看到,我们自己插入的记录都是普通记录,它们的record_type值都是0,最小记录和最大记录的record_type值分别是2和3。 next_record 当前记录的实际数据到下一条记录的实际数据的地址偏移量。
注意:==下一条记录并不是指按照我们插入顺序的下一条记录==,而是指按照主键值从小到大的顺序的下一条记录==。还规定下确界记录(即最小记录)的下一条记录为本页主键值最小的用户记录,本页主键值最大的用户记录的下一条记录page就是Supremum记录(也就是最大的记录) image-5 从图中可以看出,我们的记录按照主键升序形成了一个单向链表。
最大记录的next_record值为0,表示最大记录没有下一条记录。它是这个单链表中的最后一个节点。
那么我们来试试这个语句: 代码语言:sql copy mysql> DELETE FROM page_demo WHERE c1 = 2;查询OK,1行受影响(0.02秒) 代码语言:txt copy image-2 删除第2条记录前后主要发生了什么这些变化:第二条记录没有从存储空间中删除,但该记录的delete_mask值为设置为1。第二条记录的next_record值变为0,表示该记录没有下一条记录。
记录1的next_record指向记录3。你可能忽略的另一件事是,最大记录的n_owned值从5变成了4。
所以,无论我们如何添加、删除或修改页面中的记录,InnoDB将始终维护一个单链表的记录。中的各个节点按照主键值从小到大的顺序连接。
为什么指针指向中间?这个位置刚刚好。往左读是记录头信息,往右读才是真正的数据。
而且,可变长度字段长度列表和NULL值列表本身是相反的顺序。这样它们就被放置得更靠近对应的字段,这大大提高了缓存命中率。
如果我们这个时候插入删除的会怎么样呢? (七进七出?nnd,可以玩了!)我们来试试代码语言:sql copy mysql> INSERT INTO page_demo VALUES(2, , 'bbbb');------------ ----------------------------------查询 OK,1 行受影响 (0.00 秒)image-1 可以从图中看出,InnoDB并没有因为新记录的插入而申请新的存储空间,而是直接复用原来删除的记录的存储空间。Tips:在介绍delete_mask时提到过,当数据页中有多条删除记录时,这些记录的next_record属性会形成这些删除记录的垃圾链表,以备将来使用。
重复利用这个存储空间。页面目录记录按照页面中主键值的升序连接成单链表。
那么如果我们想根据主键值来查找页面中的一条记录该怎么办呢?例如这个查询语句: 代码语言:sql copy SELECT * FROM page_demo WHERE c1 = 3;先说最暴力的方法,从最小的记录一路往回查找,找到第一个比当前记录大的就停止。时间复杂度为$O(N)$。
学过算法的人都知道,这样找小数据是没有问题的。那么有没有更优化的方法呢?两点?有所作为吗?为了方便大家理解,我们先举一个简单的例子。
例如,人们在看电影时通常要查找车牌号时,通常会先看剧集,然后选择国产、日韩、欧美(你走错片场了),然后再查找车牌号事实上,InnoDB也执行类似的目录过程如下:将所有正常记录(包括最大和最小记录,不包括标记为删除的记录)分为几组。每个组的最后一条记录(即该组中最大的记录)的头信息中的n_owned属性表示该记录有多少条记录,即该组中有多少条记录。
(所以,看到这里你就明白了)分别提取每组最后一条记录的地址偏移量,并按顺序存放在页尾附近。这个地方就是所谓的Page Directory,也就是页面目录(此时你应该回去看看页面各部分的图片)。
页目录中的这些地址偏移量称为槽(英文名称:Slot),因此这个页目录就是由槽组成的。image-7 我不知道你是否有任何问题,但我有! ! !最小记录的n_owned为1可以理解,一直到第4条记录也是可以理解的。
但最大的记录为什么还是5呢?因为最小记录只有它自己,所以可以理解,这里最大记录的自己实际上包括最大记录本身和插入的四个数据。为什么会这样设置呢?一如既往,请记住您的问题!这里我们也可以理解image-7。
突然感觉上面的问题稍微好理解了一点: smiley: 其实这就得说到Innodb官方的一条规则==最小记录所在的组中只能有1条记录。最大记录所在组的记录数只能在1到8之间,其余组的记录数只能在4到8之间。
==让我们揭开帷幕,看看完整的步骤吧!最初,一个数据页中只有两条记录,最小记录和最大记录,并且它们分属于两个组。此后每次插入一条记录,都会从页目录中找到一个主键值大于该记录主键值且相差最小的槽位,然后将该槽位对应的记录的n_owned值加1,表示该组又添加了一条新记录。
一条记录,直到组中的记录数等于 8。当组中的记录数等于 8 后插入一条记录时,组中的记录将分为两组,一组有 4 条记录,另一组有 4 条记录有 5 条记录。
这个过程会在页目录中添加一个新的槽来记录新组中最大记录的偏移量。这里我们插入多组看看代码语言: sql copy mysql> INSERT INTO page_demo VALUES(5, , 'eeee'), (6, , 'ffff'), (7, , 'gggg'), ( 8, , 'hhhh'), (9, , 'iiii'), (10, , 'jjjj'), (11, , 'kkkk'), (12, , 'llll'), (13, , 'mmmm '), (14, , 'nnnn'), (15, , 'oooo'), (16, , 'pppp');查询正常,12行受影响(0.00秒)---------- ----------------------------------------------------------- --- ----记录: 12 重复: 0 警告: 0 这里我们插入12组数据,加上我们之前插入的最大和最小记录,总共18组。
我们看一下内存结构图。 Image-6 拥有的是 0?如上所述,您添加的是一个插槽。
至于你是否分拥有,很容易计算。等分然后/2就可以了,因为把16条记录的信息都画在一张图中太占空间了。
,让人眼花缭乱,所以只保留了用户记录头信息中的n_owned和next_record属性,也省略了每条记录之间的箭头。没有图并不代表没有!现在我们来谈谈搜索。
事实上,一切都在这里。您可以使用暴力破解或二进制拆分。
呵呵,没必要用蛮力。只需一一比较主键大小即可。
我们在这里谈论二进制分裂是什么?有两点你不知道吗?百度一下,我这里假装大家都掌握了基本的二分查找(~呃呃呃,大部分人学完就知道了~)假设我们要找到id=6的数据来计算中间槽=(0 )/2=2 id=8 8>6 h=2 再次计算中间槽 = (0+h)/2=2 id=4 4<6 l=1h-l=1,确保在槽= 2、直接遍历就可以了(~小数据可以分成两块~)所以在一个数据页中查找指定主键值的记录的过程分为两步。:通过二分法确定记录所在槽位,找到该槽位中主键值最小的记录。
通过记录的next_record属性遍历slot所在组中的记录。这里对算法能力有一些要求。
如果你对二分查找不清楚,可以阅读我之前的文章。我不知道我是否写过它们。
反正百度都有。二分搜索仍然是计算机科学中非常重要的思想。
此外,还得出了一个二分答案。今年的ACM比赛中也使用了这个算法。
如果Page Header行有一个标题,那么该页面有一个标题就不足为奇了。其实这个header不仅仅是获取一些数据,一共56个字节。
image-9 大家一定清楚从PAGE_N_DIR_SLOTS到PAGE_LAST_INSERT和PAGE_N_RECS的含义。如果没有,我很抱歉,你应该回去再试一次。
PAGE_DIRECTION 和 PAGE_N_DIRECTION: PAGE_DIRECTION 如果新插入的记录的主键值大于前一条记录的主键值,我们就说这条记录的插入方向是向右的,反之亦然。用于指示最后一条记录的插入方向的状态是PAGE_DIRECTION。
PAGE_N_DIRECTION 假设连续几次插入新记录的方向一致,InnoDB会记录同一方向插入的记录数。该数字由 PAGE_N_DIRECTION 状态表示。
当然,如果最后一条记录的插入方向发生变化,则该状态的值会被清除并重新计数。至于剩下的与索引相关的内容,我们稍后再讲索引的时候再讨论。
下面黄色标注的就是你现在应该掌握的内容。
版权声明:本文内容由互联网用户自发贡献,本站不拥有所有权,不承担相关法律责任。如果发现本站有涉嫌抄袭的内容,欢迎发送邮件 举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。
标签:
相关文章
06-18
06-18
06-18
06-18
06-18
06-18
06-18
最新文章
【玩转GPU】ControlNet初学者生存指南
【实战】获取小程序中用户的城市信息(附源码)
包雪雪简单介绍Vue.js:开学
Go进阶:使用Gin框架简单实现服务端渲染
线程池介绍及实际案例分享
JMeter 注释 18 - JMeter 常用配置组件介绍
基于Sentry的大数据权限解决方案
【云+社区年度征文集】GPE监控介绍及使用