Mysql索引基础B-tree以及B+tree

1. B-tree以及B+tree的原理

1. B-tree

  • B 树是为了磁盘或其它存储设备而设计的一种多叉(相对于二叉,B树每个内结点有多个分支,即多叉)平衡查找树。
  • 结构描述
    Mysql索引基础B-tree以及B+tree_第1张图片

  • 结构特点

    1. 一个节点为一个二元组[key, data]
    2. 每个叶子节点最少包含一个key和两个指针,最多包含2d-1个key和2d个指针
    3. 叶子节点的指针均为null
    4. 所有叶节点具有相同的深度,等于树高h
    5. 每个节点中,key和指针互相间隔,节点两端是指针
    6. 一个节点中的key从左到右非递减排列
  • 在 B-Tree 中按 key 检索数据的算法非常直观:首先从根节点进行二分查找,如果找到则返回对应节点的 data,否则对相应区间的指针指向的节点递归进行查找,直到找到节点或找到 null 指针,前者查找成功,后者查找失败。以下图为例
    Mysql索引基础B-tree以及B+tree_第2张图片

  • 每个节点占用一个盘块的磁盘空间(也可以说是一个页),一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。
  • 两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。
  • 模拟查找关键字29的过程:
    1. 根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】
    2. 比较关键字29在区间(17,35),找到磁盘块1的指针P2。
    3. 根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】
    4. 比较关键字29在区间(26,30),找到磁盘块3的指针P2。
    5. 根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】
    6. 在磁盘块8中的关键字列表中找到关键字29。
  • 分析上面过程,发现只需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。

2. B+tree

  • B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。
  • 从上一节中的B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。
  • 在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。
  • 一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 基础上进行了优化,在叶子节点增加了顺序访问指针,做这个优化的目的是为了提高区间访问的性能。
  • 结构描述
    Mysql索引基础B-tree以及B+tree_第3张图片

使用B+tree的优势:

  1. B+tree的磁盘读写代价更低:B+-tree的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
  2. 由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

3. 总结:为什么使用B-tree和B+tree

  • 红黑树等数据结构也可以用来实现索引,但是文件系统及数据库系统普遍采用 B-/+Tree 作为索引结构。
  • 页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为 4k),主存和磁盘以页为单位交换数据。
  • 一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。为了减少磁盘 I/O,磁盘往往不是严格按需读取,而是每次都会预读。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。
  • 数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页(nnoDB存储引擎中默认每个页的大小为16KB),这样每个节点只需要一次 I/O 就可以完全载入。B-Tree 中一次检索最多需要 h-1 次 I/O(根节点常驻内存),渐进复杂度为 O(h)=O(logdN)。一般实际应用中,出度 d 是非常大的数字,通常超过 100,因此 h 非常小(通常不超过 3)。而红黑树这种结构,h 明显要深的多。并且于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,效率明显比 B-Tree 差很多。
  • B+Tree 更适合外存索引,原因和内节点出度 d 有关。由于 B+Tree 内节点去掉了 data 域,因此可以拥有更大的出度(也就是说每个节点可以存储更多的指针,指向的数据),拥有更好的性能。

2. Mysql的索引实现

  • Mysql中,MyISAM和InnoDB两个存储引擎的索引都采用B+tree这个数据结构,但是他们有很大区别

1. MyISAM引擎

Mysql索引基础B-tree以及B+tree_第4张图片

  • 这里设表一共有三列,假设我们以Col1为主键,则图8是一个MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中,主索引辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。
  • 同样也是一颗B+Tree,data域保存数据记录的地址。因此,MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。
  • MyISAM的索引方式也叫做“非聚集”的,之所以这么称呼是为了与InnoDB的聚集索引区分。

2. InnoDB引擎

  • 虽然InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同。
  • 区别1:是InnoDB的数据文件本身就是索引文件。从上文知道,MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引
    Mysql索引基础B-tree以及B+tree_第5张图片

  • 这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。

  • 区别2: 与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址InnoDB的所有辅助索引都引用主键作为data域。例如,下图为定义在Col3上的一个辅助索引:
    Mysql索引基础B-tree以及B+tree_第6张图片

  • 聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。

了解不同存储引擎的索引实现方式对于正确使用和优化索引都非常有帮助

  • 例如,知道了InnoDB的索引实现后,就很容易明白为什么不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。
  • 例如,用非单调的字段作为主键在InnoDB中不是个好主意,因为InnoDB数据文件本身是一颗B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。

参考:
MySQL索引背后的数据结构及算法原理
MYSQL索引原理
《高性能mysql》

你可能感兴趣的:(Mysql与数据库)