数据库索引结构总结

[TOC]

参考

数据库索引数据结构总结

本文摘抄自数据库索引数据结构总结

1. 摘要

数据库索引是数据库中最重要的组成部分,而索引的数据结构设计对数据库的性能有重要的影响。本文尝试选取几种典型的索引数据结构,总结分析,以窥数据库索引之全貌。

2. B+Tree

B+Tree 是一种树数据结构,是一个n叉排序树,每个节点通常有多个孩子,一棵B+Tree包含根节点、内部节点和叶子节点。根节点可能是一个叶子节点,也可能是一个包含两个或两个以上孩子节点的节点。

B+Tree 几乎是数据库默认的索引实现,其细节如下:

维基百科在 B+ 树中的节点通常被表示为一组有序的元素和子指针。如果此B+树的序数(order)是m ,则除了根之外的每个节点都包含最少m/2个元素最多 m-1 个元素,对于任意的节点有最多 m 个子指针。对于所有内部节点,子指针的数目总是比元素的数目多一个。因为所有叶子都在相同的高度上,节点通常不包含确定它们是叶子还是内部节点的方式。
每个内部节点的元素充当分开它的子树的分离值。例如,如果内部节点有三个子节点(或子树)则它必须有两个分离值或元素 a1 和 a2。在最左子树中所有的值都小于等于 a1,在中间子树中所有的值都在 a1 和 a2 之间((a1,a2]),而在最右子树中所有的值都大于 a2。

B+Tree 有如下性质:

  • 查询时间复杂度为 O(logmn)
  • 插入时间复杂度 O(logmn)
  • 删除时间复杂度 O(logmn)
  • 搜索一个范围的键(k 个键)时间复杂度为 O(logmn+k)

B+ Tree 的多线程同步:

  • 搜索:从根节点开始,获取子节点的读闩,然后释放父节点的读闩;重复这个过程,直到找到目标节点位置。
  • 插入/删除:从根节点开始,获取子节点的写闩;重复这个过程,直到找到目标节点位置;如果子节点是安全的,插入/删除不会引起树结构的变化即父节点不需要调整,可释放所有祖先写闩;乐观的插入/删除是先走搜索获得目标节点的读闩,如果目标节点并不安全,则回归上述从根节点获得写闩的过程。

3. Skip List(跳表)

Skip List是一种随机化的数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作需要O(log n)平均时间)。基本上,跳跃列表是对有序的链表增加上附加的前进链接,增加是以随机化的方式进行的,所以在列表中的查找可以快速的跳过部分列表(因此得名)。所有操作都以对数随机化的时间进行。Skip List可以很好解决有序链表查找特定值的困难。

一个跳表,应该具有以下特征:

  • 一个跳表应该有几个层(level)组成;
  • 跳表的第一层包含所有的元素;
  • 每一层都是一个有序的链表;
  • 如果元素x出现在第i层,则所有比i小的层都包含x;
  • 第i层的元素通过一个down指针指向下一层拥有相同值的元素;
    在每一层中,-1和1两个元素都出现(分别表示INT_MIN和INT_MAX);
  • Top指针指向最高层的第一个元素。

相对于 B+Tree,Skip List 有如下优势:

  • B+ Tree 的插入删除操作有可能会引起树结构的变化,需要从新平衡;与之相对的,跳表插入要简单的多,更加简单高效。
  • B+ Tree 的实现诸如保持树平衡非常复杂;与之相对的,跳表并没有非常复杂的逻辑,实现相对更加简单。
  • 取下一个元素可以在常数时间内,相对于 B+ Tree 的对数时间。
  • 因为链表非常简单,可以很容易的修改跳表结构,以更好地支持诸如范围索引之类的操作。
  • 链表结构使得多线程修改可以仅用 CAS 保证原子性,从而避免重量级的同步机制。
  • 链表的持久化更加简单。

跳表看起来非常像树,比如说检索

跳表横向看来是有很多链表组成,然而指针跳转对于 CPU 缓存 来讲非常不友好,可以用纵向数组来实现跳表以增加 CPU 缓存。

4. Bw-Tree

Hekaton 是微软 SQLServer 专门针对 OLTP 应用场景进行优化的数据库引擎,其索引实现基于 Bw-Tree。Bw-Tree 是一种无需使用任何闩同步的 B+Tree,其主要设计思想如下:

  • Mapping Table(映射表) 映射表存储内存页的ID与其对应的物理内存地址,使得线程可以通过访问映射表找到需要方位的内存地址,映射表的更新通过CAS操作。
  • 不直接修改节点,任何的更新操作都会生成新的数据并通过指针指向被更新节点;新生成的数据所导致的元数据的修改,比如修改映射表都通过 CAS 完成。
  • 垃圾回收,Bw-Tree 通过不断新增数据的方式避免直接修改树节点,在树不断更新的过程中,不可避免的会产生很多垃圾,因此 Bw-Tree 实现了基于 Epoch 的垃圾回收机制:当一个线程想保护一个它正在使用但是将会被回收的对象,例如检索的时候,访问了一个内存页,就把当前线程加入 Epoch,当这个线程完成检索页面的操作后,就会退出 Epoch。通常一个线程在一个epoch的时间间隔内完成一次操作,例如检索。在线程成功加入 Epoch 的时候,可能会看到将要被释放的老版本的对象,但不可能看到已经在前一个 Epoch 中释放的对象,因为其在当前 Epoch 中的操作并不依赖上一个 Epoch 中的数据。因此,一旦所有的线程成功加入Epoch 并完成操作然后退出这个Epoch,回收该 Epoch 中的所有对象是安全的。

由于维护了映射表,和新增数据链,因此树结构调整相对复杂,不仅仅要调整树,切要保证树结构和映射表之间的关系。具体操作可参考此篇文章

尽管实现非常复杂,Bw-Tree 作为无锁的数据库索引树,有如下优势:

  • 无闩: 实现无锁数据结构十分困难,Bw-Tree 在多线程场景下没有引入任何的闩,只使用 CAS 指令保证线程同步,因此多核的扩展性优于普通用闩同步的B+Tree。
  • CPU 缓存: 由于不直接修改节点而是追加修改补丁,因此 CPU 缓存不会因为更新数据而失效,因此可以显著提高 CPU 缓存命中率。微软论文中的数据表明,90% 的读操作数据来自 CPU L1/L2 缓存。

5. Adaptive Radix Tree(自适应基数/前缀树,ART)

Radix Tree (基数树)是一种常见的前缀树,Linux Kernel 文件系统就用到了该数据结构:

image.png

Hyper 数据库中实现了 Adaptive Radix Tree (自适应基数/前缀树,ART)作为其索引。基数树的每个节点可以存储任意长度的键切片,比如 Linux Kernel 中的基数树每个节点存储 6位的键切片;然而数据库索引很多场景下会被频繁修改,每个节点固定长度的键切片会造成时间(切片过长)和空间上(切片过短)的浪费,因此,Hyper 实现了自适应的基数树,也就是节点根据长度的不同分成若干种,随着数据的变化而自行调整。

ART 结构:


ART 数据节点类型:


其主要特点有:

  • 树的高度仅取决于键长度。
  • 更新和删除不涉及到树结构的调整,不需要平衡操作。
  • 到达叶子节点的路径就是键。
  • 时间复杂度取决于键的长度,而跟数据量无关,如果数据的增加远远超过键长度的增加,那么使用 ART 将会在性能上带来非常大的收益。

讲述ART同步的论文中提供了描述了两种ART的同步机制:

  1. 乐观锁:
    • 读不阻塞写
    • 写操作在获得对应的节点闩之后,更新版本信息
    • 读操作在读下一个结点前,检查版本信息是否发生改变
  2. 乐观读悲观写
    • 所有的节点都包含一个互斥锁,当某一个读操作获得此互斥锁之后,阻塞其他写操作
    • 读操作不用获取任何的锁或者闩,也不用检查版本信息
    • 写操作保证同一个节点读操作的数据一致性,即写操作使用原子指令进行写入

6. Masstree

2012年发表的论文 Cache craftiness for fast multicore key-value storage 提出了 Masstree,其特点如下:

  • 可以理解为B+ Tree 和 Radix Tree 的混合体,即将键切分成多个部分,每个部分为一个节点;每个节点内部又是一个 B+ Tree,兼顾空间和性能。


  • Masstree将变长键划分成多个固长部分,每个固长部分可以通过int类型表示,而不是char类型。由于处理器处理int类型比较操作的速度远远快于char数组的比较,因此Masstree通过int类型的比较进一步加速了查找过程。固定长度可以设置为 CPU 缓存行长度,以增加 CPU 缓存效率。
  • 每个节点是一个 B+ Tree,因此 CPU 在查询的时候可以将节点所代表的B+ Tree 加载到 CPU 缓存中,以增加 CPU 缓存命中率。
  • 其并发控制用到了Read-Copy-Update(RCU)。读不因任何数据更新而阻塞,但更新数据的时候,需要先复制一份副本,在副本上完成修改,再一次性地替换旧数据。因此读不会造成 CPU 缓存无效。

7. 性能对比

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容