数据库常见索引解析(B树,B-树,B+树,B*树,位图索引,Hash索引)

2023-05-16

B树

       即二叉搜索树:

       1.所有非叶子结点至多拥有两个儿子(Left和Right);

       2.所有结点存储一个关键字;

       3.非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树;

       如:

       

       B树的搜索,从根结点开始,如果查询的关键字与结点的关键字相等,那么就命中;否则,如果查询关键字比结点关键字小,就进入左儿子;如果比结点关键字大,就进入右儿子;如果左儿子或右儿子的指针为空,则报告找不到相应的关键字;

       如果B树的所有非叶子结点的左右子树的结点数目均保持差不多(平衡),那么B树的搜索性能逼近二分查找;但它比连续内存空间的二分查找的优点是,改变B树结构(插入与删除结点)不需要移动大段的内存数据,甚至通常是常数开销;

       如:

      

   但B树在经过多次插入与删除后,有可能导致不同的结构:

  

   右边也是一个B树,但它的搜索性能已经是线性的了;同样的关键字集合有可能导致不同的树结构索引;所以,使用B树还要考虑尽可能让B树保持左图的结构,和避免右图的结构,也就是所谓的“平衡”问题;      

       实际使用的B树都是在原B树的基础上加上平衡算法,即“平衡二叉树”;如何保持B树结点分布均匀的平衡算法是平衡二叉树的关键;平衡算法是一种在B树中插入和删除结点的策略。常见的平衡二叉树有:AVL,RBT,Treap,Splay Tree。

B-树

是一种多路搜索树(并不是二叉的):

       1.定义任意非叶子结点最多只有M个儿子;且M>2;

       2.根结点的儿子数为[2, M];

       3.除根结点以外的非叶子结点的儿子数为[M/2,M];

       4.每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)

       5.非叶子结点的关键字个数=指向儿子的指针个数-1;

       6.非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];

       7.非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;

       8.所有叶子结点位于同一层;

       如:(M=3)


  B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点;

B-树的特性:

       1.关键字集合分布在整颗树中;

       2.任何一个关键字出现且只出现在一个结点中;

       3.搜索有可能在非叶子结点结束;

       4.其搜索性能等价于在关键字全集内做一次二分查找;

       5.自动层次控制;

       由于限制了除根结点以外的非叶子结点,至少含有M/2个儿子,确保了结点的至少利用率,其最底搜索性能为:


其中,M为设定的非叶子结点最多子树个数,N为关键字总数;

       所以B-树的性能总是等价于二分查找(与M值无关),也就没有B树平衡的问题;

       由于M/2的限制,在插入结点时,如果结点已满,需要将结点分裂为两个各占M/2的结点;删除结点时,需将两个不足M/2的兄弟结点合并;

B+树

  B+树是B-树的变体,也是一种多路搜索树:

       1.其定义基本与B-树同,除了:

       2.非叶子结点的子树指针与关键字个数相同;

       3.非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间);

       5.为所有叶子结点增加一个链指针;

       6.所有关键字都在叶子结点出现;

       如:(M=3)


B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找;

       B+的特性:

       1.所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;

       2.不可能在非叶子结点命中;

       3.非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;

       4.更适合文件索引系统;


B*树

是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针;


B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2);

       B+树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针;

       B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;

       所以,B*树分配新结点的概率比B+树要低,空间使用率更高;

小结

       B树:二叉树,每个结点只存储一个关键字,等于则命中,小于走左结点,大于走右结点;

       B-树:多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点;

       所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中;

       B+树:在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中;

       B*树:在B+树基础上,为非叶子结点也增加链表指针,将结点的最低利用率从1/2提高到2/3


位图索引

1.案例

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

姓名(Name)

性别(Gender)

婚姻状况(Marital)

张三

已婚

李四

已婚

王五

未婚

赵六

离婚

孙七

未婚

...

...

...

1)不使用索引

  不使用索引时,数据库只能一行行扫描所有记录,然后判断该记录是否满足查询条件。

2)B树索引

  对于性别,可取值的范围只有'男','女',并且男和女可能各站该表的50%的数据,这时添加B树索引还是需要取出一半的数据, 因此完全没有必要。相反,如果某个字段的取值范围很广,几乎没有重复,比如身份证号,此时使用B树索引较为合适。事实上,当取出的行数据占用表中大部分的数据时,即使添加了B树索引,数据库如oracle、MySQL也不会使用B树索引,很有可能还是一行行全部扫描。

2.位图索引出马

如果用户查询的列的基数非常的小, 即只有的几个固定值,如性别、婚姻状况、行政区等等。要为这些基数值比较小的列建索引,就需要建立位图索引。

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

RowId

1

2

3

4

5

...

1

0

1

0

0

 

0

1

0

1

1

 ...

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

RowId

1

2

3

4

5

...

已婚

1

1

0

0

0

 

未婚

0

0

1

0

1

 

离婚

0

0

0

1

0

 

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

RowId

1

2

3

4

5

1

0

1

0

0

and

 

 

 

 

 

未婚

0

0

1

0

1

结果

0

0

1

0

0

3.位图索引适应场景

上面讲了,位图索引适合只有几个固定值的列,如性别、婚姻状况、行政区等等,而身份证号这种类型不适合用位图索引。

  此外,位图索引适合静态数据,而不适合索引频繁更新的列。举个例子,有这样一个字段busy,记录各个机器的繁忙与否,当机器忙碌时,busy1,当机器不忙碌时,busy0

  这个时候有人会说使用位图索引,因为busy只有两个值。好,我们使用位图索引索引busy字段!假设用户A使用update更新某个机器的busy值,比如update table set table.busy=1 where rowid=100;,但还没有commit,而用户B也使用update更新另一个机器的busy值,update table set table.busy=1 where rowid=12; 这个时候用户B怎么也更新不了,需要等待用户A commit

  原因:用户A更新了某个机器的busy值为1,会导致所有busy1的机器的位图向量发生改变,因此数据库会将busy1的所有行锁定,只有commit之后才解锁。


Hash索引

索引列会被存储在匹配到的hash bucket里面的表里,这个表里会有实际的数据行指针,再根据实际的数据行指针查找对应的数据行。


概括来说,要查找一行数据或者处理一个where子句,SQL Server引擎需要做下面几件事

1、根据where条件里面的参数生成合适的哈希函数

2、索引列进行匹配,匹配到对应hash bucket,找到对应hash bucket意味着也找到了对应的数据行指针(row pointer

3、读取数据

哈希索引比起B树索引简单,因为它不需要遍历B树,所以访问速度会更快


Hash索引的缺点:

1、因为Hash索引比较的是经过Hash计算的值,所以只能进行等式比较,不能用于范围查询

2、由于哈希值是按照顺序排列的,但是哈希值映射的真正数据在哈希表中就不一定按照顺序排列,所以无法利用Hash索引来加速任何排序操作

3、不能用部分索引键来搜索,因为组合索引在计算哈希值的时候是一起计算的。

4、当哈希值大量重复且数据量非常大时,其检索效率并没有Btree索引高的。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据库常见索引解析(B树,B-树,B+树,B*树,位图索引,Hash索引) 的相关文章

  • Qt 计算和比较密码哈希

    目前正在 Qt 中为测验程序构建面向 Web 的身份验证服务 据我了解 在数据库中存储用户密码时 必须对其进行隐藏 以防落入坏人之手 流行的方法似乎是添加的过程Salt https en wikipedia org wiki Salt cr
  • 使用javascript向url添加哈希而不滚动页面?

    在不滚动页面的情况下向 url 添加哈希 使用 JavaScript 我打开页面 我向下滚动 我单击添加哈希的链接 可能带有值 test 示例 http www example com test http www example com t
  • 将 Python 中的 SHA 哈希计算转换为 C#

    有人可以帮我将以下两行 python 代码转换为 C 代码吗 hash hmac new secret data digestmod hashlib sha1 key hash hexdigest 8 如果您有兴趣 其余的看起来像这样 us
  • 获取express.js中间件请求中“#”后的url

    我需要获取服务器中间件上的 url 使用express js 我用req url但是当 url 开头时 some urlreq url 返回 与req path 有没有办法获取url之后 在express js中 No URL 中以 符号永
  • 比较 ruby​​ 哈希值[重复]

    这个问题在这里已经有答案了 可能的重复 如何比较两个哈希值 https stackoverflow com questions 4928789 how do i compare two hashes 我有两个 ruby 哈希值 本质上是模型
  • 使用 crypt() 加密

    我目前正在做一个非常安全的登录系统 但我是 crypt 函数的新手 需要一些快速帮助 我在注册过程中使用 crypt 加密密码字符串并将其保存到数据库中 但是 我如何在登录过程中解密密钥 或者我应该怎么做 或者是否可以对提交的密码字符串进行
  • 按值和键对哈希进行排序(按顺序)

    我正在寻找一种很好的方法来在 Perl 中先按值排序 然后再按键排序 Example my userids williams gt Marketing smith gt Research johnson gt Research jones
  • 从函数返回哈希值的最佳 Perl 实践是什么?

    我正在考虑将哈希引用传递给函数或从函数返回数据的最佳实践 一方面 仅将输入值传递给函数并仅返回输出变量似乎很直观 然而 在 Perl 中传递哈希值只能通过引用来完成 因此有点混乱 而且似乎更有可能犯错误 另一种方法是在输入变量中传递引用 但
  • NodeJS“加密”哈希似乎产生与 Crypto-JS javascript 库不同的输出

    我正在使用 NodeJS 的捆绑包crypto http nodejs org api crypto html crypto class hash服务器端 SHA256 哈希模块 在客户端 我使用一个名为的 javascript 库Cryp
  • md5 哈希冲突。

    如果从 1 数到 X 其中 X 是第一个与前一个数字发生 md5 冲突的数字 那么 X 是哪个数字 我想知道如果我使用 md5 作为序列号 在发生冲突之前我可以期望能够枚举多少个单元 Theoretically you can expect
  • mysql 使用什么样的哈希?

    我正在编写类似于 phpMyAdmin 的自己的代码 但我需要用户能够使用 mysql 数据库中的用户名和密码登录 我需要知道mysql数据库使用什么样的哈希来存储每个用户的密码 我检查了 dev mysql com 寻找答案 但除了以 开
  • 哈希密码字段使用什么数据类型以及长度?

    我不确定密码哈希是如何工作的 稍后将实现 但现在需要创建数据库模式 我正在考虑将密码限制为 4 20 个字符 但据我了解 加密后哈希字符串的长度将有所不同 那么 如何将这些密码存储在数据库中呢 更新 仅使用哈希函数不足以存储密码 你应该阅读
  • 当我使用加盐 CRYPT_MD5 加密我的密码时,正在加密什么?

    对字符串使用 md5 总是会产生字母数字加密结果 即 没有符号 然而 当我使用 php crypt 函数 特别是带有盐的 CRYPT MD5 并且它已打开 我已经检查过 时 它返回的假定 md5 哈希看起来不像 md5 哈希 例如 如果我
  • 散列密码的最佳实践 - SHA256 还是 SHA512?

    我目前正在使用 SHA256 和盐来哈希我的密码 继续使用 SHA256 更好还是应该更改为 SHA512 切换到 SHA512 几乎不会让您的网站更安全 您不应该编写自己的密码哈希函数 相反 使用现有的实现 SHA256 和 SHA512
  • 从 HoA 值中获取独特元素并打印

    我有一个 HoA 其中包含某些值 我只需要 HoA 中的独特元素 预期结果 Key 1 Element ABC DEF Key 2 Element XYZ RST Key 3 Element LMN 下面是我的脚本 usr bin perl
  • 散列 hash_hmac 时,Convert.ToChar(0) 散列结果与 PHP 中的 chr(0) 不同的字符串

    我在 PHP 中有一个字符串 它被转换为字节数组并进行哈希处理 转换为字节数组的字符串如下所示 G 字符 0 便便 我需要 C 中的等效字节数组 这样我才能得到相同的哈希值 编辑 这是完整的问题 生成的哈希值不同 PHP api secre
  • 哪些可移植性问题与 C 中指针的字节级访问相关?

    Purpose 我正在为一个较大的项目编写一个小型库 它提供 malloc realloc free 包装函数以及一个可以告诉您其参数 类型为void 对应于由库的包装函数分配和管理的活动 尚未释放 内存 我们将此函数称为isgood me
  • 如何添加到 Ruby 中的现有哈希

    关于添加一个key gt value与 Ruby 中现有的填充哈希配对 我正在学习 Apress 的 Beginning Ruby 并且刚刚完成了哈希章节 我试图找到最简单的方法来使用哈希实现与数组相同的结果 x 1 2 3 4 x lt
  • 使用您正在散列的内容的散列作为盐?

    假设用户注册了您的网站 您对他们选择的密码进行哈希处理 然后使用该哈希值作为盐 并使用该盐重新哈希其密码 Example String hash1 MD5 password String endHash MD5 hash1 password
  • 改变字典的哈希函数

    按照此question https stackoverflow com questions 37100390 towards understanding dictionaries 我们知道两个不同的字典 dict 1 and dict 2例

随机推荐