写在前面:我一直认为数据库中已经规定,一个页面中至少要存储2条记录,但是前几天碰到一个问题:为什么INNODB数据页面中最少存储2条记录?一下子把我问住了,知道是这样,但从没有问过自己这是为什么,所以我一直想这个问题,现在刨根问底,终于知道为什么了,下面就把这个问题的原因说一下吧。
在INNODB中,底层数据的存储是通过B+树实现的,一个页面的大小一般设置为16K,不同页面之间通过父子、兄弟挂链,形成B+树结构。
为了说明为什么一个数据页面至少要存储2条记录,下面就先简单叙述一下B树的一个记录插入过程,下面图示中的方框用来表示一个页面,左边和右边的小方框表示最小虚记录及最大虚记录,下面是表示内节点中的KEY指针,每一个KEY都会有一个指针指向其叶子节点的位置,对于叶子节点,这个是没有的。下面假设插入数据的顺序是2-1-3-1.5。
假设一个页面中可以存储3条记录,下就面插入过程图示说明:
现在根页面已经插入满了,此时如果再继续插入,则需要将根页面分裂(根节点的分裂),此时创建一个新的叶子节点,然后将原根页面的全部记录复制到新页面中,新根页面的最小虚记录要指向原来的根页面(新叶子节点),同时将原根页面中的记录全部删除,结果是这样的:
然后继续向原页面(新的叶子节点)插入数据1.5,因为还是插入不成功(因为页面还是满的),则继续分裂,但此时的分裂是叶子节点的分裂,此时再创建一个空的叶子节点页面,并且将被分裂页面的数据的一部分移动到新页面中,同时将新页面及被分裂叶子页面之间形成链表,另外,还需要在新建叶子节点中找到最左边的记录,根据这条记录创建一个其父节点中的关系字KEY值,同时让这个KEY键中的指针指向这个新的叶子节点,操作后如下:
那么此时就可以向重新搜索插入位置,那么找到的是已经有1这条记录的页面,此时页面不满,可以插入成功,那么到现在为止,分裂已经成功。现在的图示如下:
那么上面说了插入过程,现在反回来解释为为什么一个页面中必须至少要2条记录了,假设在极端情况下,每一个页面中只能插入一条记录,那么在如下情况下,就必须要进行分裂了:
此时再向叶子节点插入数据1,结果还是插入不进去,此时分裂叶子节点,创建新页面,新增一个2节点的右兄弟,而此时因为插入的是1,比2小,所以需要将2的数据复制到新的叶子页面中,所以插入1之后的结果如下:
此时1已经插入完成,再插入3,通过B树的搜索,找到了插入位置为叶子节点2里面,但是因为只能插入一个记录,所以这个节点再分裂,因为插入的3比2大,直接将3写入新叶子节点即可,结果如下:
此时还是同样的道理,需要将3这个节点在内节点中创建索引键指针,将3插入到根页面中,但是根页面中2已经存在了,再不能插入更多的数据记录,根页面再次分裂,同样的道理,创建一个新的根页面,然后再次向分裂页面插入数据3,还是插入不成功,再次分裂内节点,然后3可以顺利插入完成,那么此时的情况是下面这样的:
看看这个图,是不是觉得很怪,怎么这么多节点其实只有三个记录,是的,如果一个页面只能插入一条记录的话就是这样的,所以这也是为什么做了限制的原因了。
但是还有一种情况,现在将范围扩大一些,如果KEY的大小限制了,一个内节点页面中至少要存储2条记录(这里假设就是存储2条记录),而叶子节点中只能存储一条记录,那么此时的结果其实就是一个非常冗余的具有B树结构的双链表,比如插入顺序为1,2,3,4的话:
总结:这个问题在数据库中是这样的解释,但归根到底,其实还是B+树的特性,如果不存储至少2条记录,则这个B+树是没有意义的,形不成一个有效的索引,所以这也是一种解释吧。
上面的叙述大致原理是这样的,但是如果有些细节不正确地方,请指点或者提出来。
那么至于记录超长时,在INNODB中的处理方式是什么,这个问题后面可以继续做一些叙述,这个问题先就这样吧。