如果再有人问你数据库的原理,把这篇文章给他( 四 )


上文说的很抽象,我们回来看看我们的问题 。这次不用傻傻的数字了,想象一下前表中代表某人的国家的字符串 。假设你有个树包含表中的列『country』:

  • 如果你想知道谁在 UK 工作
  • 你在树中查找代表 UK 的节点
  • 在『UK 节点』你会找到 UK 员工那些行的位置
这次搜索只需 log(N) 次运算,而如果你直接使用阵列则需要 N 次运算 。你刚刚想象的就是一个数据库索引 。
B+树索引
查找一个特定值这个树挺好用,但是当你需要查找两个值之间的多个元素时,就会有大麻烦了 。你的成本将是 O(N),因为你必须查找树的每一个节点,以判断它是否处于那 2 个值之间(例如,对树使用中序遍历) 。而且这个操作不是磁盘I/O有利的,因为你必须读取整个树 。我们需要找到高效的范围查询方法 。为了解决这个问题,现代数据库使用了一种修订版的树,叫做B+树 。在一个B+树里:
  • 只有最底层的节点(叶子节点)才保存信息(相关表的行位置)
  • 其它节点只是在搜索中用来指引到正确节点的 。
【译者注:参考 B+树,二叉树遍历 维基百科】
如果再有人问你数据库的原理,把这篇文章给他

文章插图
 
你可以看到,节点更多了(多了两倍) 。确实,你有了额外的节点,它们就是帮助你找到正确节点的『决策节点』(正确节点保存着相关表中行的位置) 。但是搜索复杂度还是在 O(log(N))(只多了一层) 。一个重要的不同点是,最底层的节点是跟后续节点相连接的 。
用这个 B+树,假设你要找40到100间的值:
  • 你只需要找 40(若40不存在则找40之后最贴近的值),就像你在上一个树中所做的那样 。
  • 然后用那些连接来收集40的后续节点,直到找到100 。
比方说你找到了 M 个后续节点,树总共有 N 个节点 。对指定节点的搜索成本是 log(N),跟上一个树相同 。但是当你找到这个节点,你得通过后续节点的连接得到 M 个后续节点,这需要 M 次运算 。那么这次搜索只消耗了 M+log(N) 次运算,区别于上一个树所用的 N 次运算 。此外,你不需要读取整个树(仅需要读 M+log(N) 个节点),这意味着更少的磁盘访问 。如果 M 很小(比如 200 行)并且 N 很大(1,000,000),那结果就是天壤之别了 。
然而还有新的问题(又来了!) 。如果你在数据库中增加或删除一行(从而在相关的 B+树索引里):
  • 你必须在B+树中的节点之间保持顺序,否则节点会变得一团糟,你无法从中找到想要的节点 。
  • 你必须尽可能降低B+树的层数,否则 O(log(N)) 复杂度会变成 O(N) 。
换句话说,B+树需要自我整理和自我平衡 。谢天谢地,我们有智能删除和插入 。但是这样也带来了成本:在B+树中,插入和删除操作是 O(log(N)) 复杂度 。所以有些人听到过使用太多索引不是个好主意这类说法 。没错,你减慢了快速插入/更新/删除表中的一个行的操作,因为数据库需要以代价高昂的每索引 O(log(N)) 运算来更新表的索引 。再者,增加索引意味着给事务管理器带来更多的工作负荷(在本文结尾我们会探讨这个管理器) 。
想了解更多细节,你可以看看 Wikipedia 上这篇关于B+树的文章 。如果你想要数据库中实现B+树的例子,看看MySQL核心开发人员写的这篇文章 和 这篇文章 。两篇文章都致力于探讨 innoDB(MySQL引擎)如何处理索引 。
哈希表
我们最后一个重要的数据结构是哈希表 。当你想快速查找值时,哈希表是非常有用的 。而且,理解哈希表会帮助我们接下来理解一个数据库常见的联接操作,叫做『哈希联接』 。这个数据结构也被数据库用来保存一些内部的东西(比如锁表或者缓冲池,我们在下文会研究这两个概念) 。
哈希表这种数据结构可以用关键字来快速找到一个元素 。为了构建一个哈希表,你需要定义:
  • 元素的关键字关键字的哈希函数 。关键字计算出来的哈希值给出了元素的位置(叫做哈希桶) 。关键字比较函数 。一旦你找到正确的哈希桶,你必须用比较函数在桶内找到你要的元素 。
一个简单的例子
我们来看一个形象化的例子:
如果再有人问你数据库的原理,把这篇文章给他

文章插图
 
这个哈希表有10个哈希桶 。因为我懒,我只给出5个桶,但是我知道你很聪明,所以我让你想象其它的5个桶 。我用的哈希函数是关键字对10取模,也就是我只保留元素关键字的最后一位,用来查找它的哈希桶:


推荐阅读