索引

 

什么是索引、为什么要建立索引?

设想这样一种情况:有一百万条记录,存放在一万个数据块中,这里只考虑顺序查找,他的线性时间复杂度,可以理解为要访问一万个数据块。如果建立索引,取出每条记录的关键字和他对应的指针,组成索引表。索引表存放在一百个数据块中,只需要访问100个数据块。所以,索引极大第提升了检索的效率。

 

顺序文件上的索引

顺序文件就是记录按照某个关键字存放在块中,块内有序,块间有序(也包括块内无序,块间有序这种情况)。

 

 密集索引:每个索引对应一条记录

 

 

缺点是:索引表可能会占用存储空间。所以出现了稀疏索引。

 稀疏索引:一个索引对应一个数据块。数据在块内是无序的,但是在块间是有序的。

它既提高了检索的速度,又节省了索引表占用的空间。

 

辅助索引

考虑这样一种状况:记录在块内是无序的,在块间无序的,而且我还想用稀疏索引来提高我的检索速度,但是这个时候,采用稀疏索引是没有意义的。该怎么办呢?

 

 这个时候就出现了辅助索引:

 

 在二级稀疏索引与数据块之间,引入了密集索引,其实也可以理解为二级索引。稀疏索引与密集索引,都存放在磁盘。

思考一个问题:索引值是否可以重复?答案是肯定,索引值可以重复。来看看辅助索引是如何来处理重复索引值的:

没有间接桶的情况:

 

引入了间接桶之后:

 

 辅助索引中的间接桶应用

 

 

 

 在StdioName和Year两个属性检索上建立索引。显然他们的索引值都是重复的,而且还是大量重复。根据间接桶中的指针,将各自的记录加载进内存。然后取两个集合的交集即可。

 

索引表的维护:

显然,无论是插入一条记录,还是删除一条记录,都有可能出发索引表的插入与删除:

 

 

 而索引表又是顺序组织的,所以也会涉及到索引表元素的移动。效率比较低。而且索引表的访问也只能采用顺序访问,即一块一块地读入内存,从中查找索引项。效率不高。

 

B+树索引

什么是B+树?

我听到了两个相矛盾的版本,一个是说:m阶的B+树有m个结点,m棵子树,另一个又说,m阶的B+树有m个结点,m+1棵子树。(也可能是我理解错误,请指正)

这里我采用第二个说法。

 

叶结点

 

 中间结点

 

 

其中,所有的关键字都存储在叶结点,并且有指针指向关键字对应的记录。

 

B+树查找:

根据指针,既可以从上往下,也可以从左往右(顺序查找,但是如果顺序查找就可能不能发挥出B+树的速度优势了)

B+树的分层:一般不超过三层,根结点常驻内存,所以索引的代价不超过2,总代价不超过3.一个叶结点就是一页,一页就是一个数据块。

 

为什么会得出这个结论?

基于计算。根据你数据据块的大小、 关键字的大小、指针的大小,可以计算出:一个磁盘块能够存放多少条索引。然后进行累乘,基本上,三层B+树就可以达到上亿的数量级。

以InnoDB为例,InnoDB存储引擎默认一个数据页大小为16kb,非叶子节点存放(key,pointer),pointer为6个字节,key为4个字节,即非叶子节点能存放16kb/14左右的key,pointer,而叶子节点如果一条数据大小为100字节,那一个叶子节点大约可存放160条数据。如果高度为3,则可存放数据为:16kb/14 * 16kb/14 * 160大约1亿多数据。因此InnoDB存储引擎b+树的高度基本为2-3.

参考:https://www.cnblogs.com/tangchuanyang/p/6634581.html

 

B+树可以高效地支持范围查询。

如图:

 

 

从上到下查找,将(7,11)这个叶结点加载进内存,再将(23,29)这个叶结点加载进内存,再将这两个叶结点中间的所有叶结点都加载进内存,最后在内存就可以找出10到25之间的所有数据。

 

 

散列表:

根据key,通过hash函数计算出是哪一个“桶”。在桶中找寻块,在块中查找记录。一般:一个桶对应一个块。

 

 

散列表的维护:

插入:根据key,计算得桶,若桶已满,则建立一个溢出块;将数据存入溢出块。但是,大量溢出块的存在,势必增大io次数。

 

 

 

因此需要定期的进行io再散列,涉及到数据的大量移动。

 

删除:同样是根据key,计算得桶,从桶中将数据删除即可。这样一来,桶中就有额外的空间,可以把溢出块中的记录挪到桶中。

 

问题一:桶的数目足够大,且每桶一个块,存取 I/O最少。但是会导致很多桶没有放满,导致空间浪费。

问题二:数据的增多,会导致溢出块增多,io次数多。

因此要在时间与空间之间进行折衷。引入了动态散列表:

可扩展散列表-成倍增加

线性散列表-线性增加。

 

可扩展散列表:

key通过hash函数,转换为一个b位二进制序列,其中,前i位表示桶的编号,用以区分桶。I的数值随着记录的增多而增大。

 

 插入h(key)=1010的记录:

 

 

为什么说他是成倍增加的?

假设b=4;目前i= 2,有一个序列:11

现在把第三位也纳入桶的编号,110,111;

110也可以继续扩展:1101,1100;

111也可以继续扩展:1110,1111;

所以它的增长速度是线性的,原因在于用的是前i位来区分桶;

如果用的后i位,则可以延缓增长速度

 

规则如下:

h(k)仍是二进制位序列,但使用右边(低)i位 区分桶

1)桶数=n,h(k)的右i位=m

2)若m<n,则记录位于第m个桶

3)若n <= m < 2i(2的i次方),则记录位于第 m-2i-1(仍然是2的i次方)个桶 什么时候增加新桶:总是使n与当前记录总数r保持某个固定比例,意味着只有当桶的填充度达到或者超过某个比例后桶数才开始增长。

 

 插入h(key)=0101的记录:

 

 

 

 

 插入h(key)=0001的记录:

 

查找过程:仍然是先根据key计算出h(key),根据h(key)得到桶,在去桶中取数据。

注意:散列表天生不支持范围查询。

 

位图索引

转载自:https://www.cnblogs.com/LBSer/p/3322630.html

有张表名为table的表,由三列组成,分别是姓名、性别和婚姻状况,其中性别只有男和女两项,婚姻状况由已婚、未婚、离婚这三项,该表共有100w个记录。现在有这样的查询:     select * from table where Gender=‘男’ and Marital=“未婚”;

对于性别这个列,位图索引形成两个向量,男向量为10100...,向量的每一位表示该行是否是男,如果是则位1,否为0,同理,女向量位01011。

 

 

 对于婚姻状况这一列,位图索引生成三个向量,已婚为11000...,未婚为00100...,离婚为00010...。

 

 当我们使用查询语句“select * from table where Gender=‘男’ and Marital=“未婚”;”的时候 首先取出男向量10100...,然后取出未婚向量00101...,将两个向量做and操作,这时生成新向量00100...,可以发现第三位为1,表示该表的第三行数据就是我们需要查询的结果。 

如果查询语句中的and改为or呢?

相应地,上述两个向量的与操作,改为或操作,得到的结果是10101,即第1,3,5个记录,他们或者是男或者是未婚。

 

什么时候适合用位图索引:

列的取值固定,易于编码;

查询的条件带有or或者and

更新操作不频繁,原因简单来说就是更新操作会导致整个向量被封锁住,影响并发。比如说:婚姻状况这个列(当然,只是举例子)频繁发生改变,当事务A将一号用户的婚姻状态由已婚改为未婚,那么已婚向量和未婚向量将同时被事务A锁定,其他事务若相修改/访问这两个向量,只能等待。因此会严重影响系统的并发度。

 

posted @ 2020-03-21 16:00  盛夏群岛  阅读(444)  评论(0)    收藏  举报