MySQL常见面试题总结

概述

在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
在这里插入图片描述
上图展示了一种可能的索引方式。左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的)。为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在O(log2n)的复杂度内获取到相应数据。

索引的优点(为什么要创建索引)

创建索引可以大大提高系统的性能。

  • 大大减少了服务器需要扫描的数据行数。
  • 帮助服务器避免进行排序和分组,以及避免创建临时表(B+Tree 索引是有序的,可以用于ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,不需要排序和分组,也就不需要创建临时表)。
  • 将随机 I/O 变为顺序 I/O。
  • 过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
  • 可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
  • 可以加速表和表之间的连接,在实现数据的参考完整性方面特别有意义。
  • 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
  • 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

建立索引的不利因素(缺点)

  • 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
  • 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
  • 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。

创建索引的准则

  • 在经常需要搜索的列上,可以加快搜索的速度;
  • 在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;
  • 在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度;
  • 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的;
  • 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;
  • 在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。
  • 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效;对于中到大型的表,索引就非常有效;但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。

哪些列不应该创建索引?

  • 对于那些在查询中很少使用或者参考的列不应该创建索引。这是因为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。
  • 对于那些只有很少数据值的列也不应该增加索引。这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。
  • 对于那些定义为text, image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。
  • 当修改性能远远大于检索性能时,不应该创建索引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因此,当修改性能远远大于检索性能时,不应该创建索引。

聚集索引和非聚集索引的区别

  • 聚集索引一个表只能有一个;而非聚集索引一个表可以存在多个。
  • 聚集索引存储记录是物理上连续存在;而非聚集索引是逻辑上的连续,物理存储并不连续。

聚集索引:物理存储按照索引排序(数据行的物理顺序与列值(一般是主键的那一列)的逻辑顺序相同);聚集索引是一种索引组织形式,索引的键值逻辑顺序决定了表数据行的物理存储顺序;

非聚集索引:物理存储不按照索引排序(索引的逻辑顺序与磁盘上行的物理存储顺序不同);非聚集索引是普通索引,仅仅只是对数据列创建相应的索引,不影响整个表的物理存储顺序。

  • 聚集索引的叶节点就是数据节点。而非聚集索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块。

聚集索引的约束是唯一性,是否要求字段也是唯一的呢?

不要求唯一。一般我们指定一个表的主键,如果这个表之前没有聚集索引,同时建立主键时候没有强制指定使用非聚集索引,SQL会默认在此字段上创建一个聚集索引,而主键都是唯一的,所以理所当然的认为创建聚集索引的字段也需要唯一。聚集索引可以创建在任何一列你想创建的字段上,这是从理论上讲,实际情况并不能随便指定,否则在性能上会是恶梦。

为什么聚集索引可以创建在任何一列上,如果此表没有主键约束,即有可能存在重复行数据呢?

如果未使用 UNIQUE 属性创建聚集索引,数据库引擎将向表自动添加一个四字节 uniqueifier 列。必要时,数据库引擎 将向行自动添加一个 uniqueifier 值,使每个键唯一。此列和列值供内部使用,用户不能查看或访问。

在主键上创建聚集索引的表在数据插入上为什么比主键上创建非聚集索引表速度要慢?

在有主键的表中插入数据行,由于有主键唯一性的约束,所以需要保证插入的数据没有重复。聚集索引由于索引叶节点就是数据页,所以如果想检查主键的唯一性,需要遍历所有数据节点才行。但非聚集索引不同,由于非聚集索引上已经包含了主键值,所以查找主键唯一性,只需要遍历所有的索引页就行(索引的存储空间比实际数据要少),这比遍历所有数据行减少了不少IO消耗。

何时使用聚集索引,何时使用非聚集索引?

在这里插入图片描述

局部性原理与磁盘预读

由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分分之一,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。程序运行期间所需要的数据通常比较集中。

由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),因此对于具有局部性的程序来说,预读可以提高I/O效率。

预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。

一般使用磁盘I/O次数评价索引结构的优劣。

B Tree

一棵m阶的B-Tree有如下特性:

  • 每个节点最多有m个孩子。
  • 除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。
  • 若根节点不是叶子节点,则至少有2个孩子
  • 所有叶子节点都在同一层,且不包含其它关键字信息
  • 每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn)
  • 关键字的个数n满足:ceil(m/2)-1 <= n <= m-1
  • ki(i=1,…n)为关键字,且关键字升序排序。
  • Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1)
    在这里插入图片描述
    每个节点占用一页的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在页的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。

模拟查找关键字29的过程:

  • 根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】
  • 比较关键字29在区间(17,35),找到磁盘块1的指针P2。
  • 根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】
  • 比较关键字29在区间(26,30),找到磁盘块3的指针P2。
  • 根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】
  • 在磁盘块8中的关键字列表中找到关键字29。

分析上面过程,发现需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。

B+ Tree

从B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。

B+Tree相对于B-Tree有几点不同:

  • 非叶子节点只存储键值信息。
  • 所有叶子节点之间都有一个链指针。
  • 数据记录都存放在叶子节点中。

在这里插入图片描述
常在B+Tree上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对B+Tree进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。

进行查找操作时,首先在根节点进行二分查找,找到一个 key 所在的指针,然后递归地在指针所指向的节点进行查找。直到查找到叶子节点,然后在叶子节点上进行二分查找,找出 key 所对应的 data。

插入删除操作会破坏平衡树的平衡性,为了保持平衡,对于新插入的键值可能需要做大量的拆分页的操作。因为B+树结构主要用于磁盘,页的拆分意味着磁盘的操作,所以应该在可能的情况下尽量减少页的拆分操作。因此,B+树同样提供了类似于平衡二叉树的旋转功能。

计算m阶,即B+ Tree该取多少阶合适?

m是怎么计算出来的呢?是根据磁盘的页大小来结算的,也就是说是由页大小决定的。磁盘的页大小是16K,MySql创建索引时,可以根据字段及类型来计算磁盘一页大概可以存多少数据。

根据官方文档描述,树高度等于2时(2阶),大概可以存两万多条数据;高度等于3时(3阶),大概可以存两千多万条数据,怎么计算的呢?

首先,1 千字节(KB)=1024 字节(B),一页有16KB,假设存主键+指针大概有14B(8+6),则一页就可以存:16*1024/(8+6)=1170 个索引了。
对于2阶的B+Tree来说,大概可以存:1170 * 16 = 18720 条数据。
对于3阶的B+Tree来说,大概可以存:1170 * 1170 * 16 = 21902400 条数据,约等于两千万。

B+ Tree 与 B Tree 的比较

  • B+树的磁盘读写代价更低:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。
  • B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
  • 由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。

红黑树与B+ Tree的比较

红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B+ Tree 作为索引结构,主要有以下几个原因:

  • 更少的查找次数

平衡树查找操作的时间复杂度和树高 h 相关, O(h)=O(logdN),其中 d 为每个节点的出度。

红黑树的出度为 2,而 B+ Tree 的出度一般都非常大,所以红黑树的树高 h 很明显比 B+ Tree 大非常多,查找的次数也就更多。

  • 利用磁盘预读特性

为了减少磁盘 I/O 操作,磁盘往往不是严格按需读取,而是每次都会预读。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的磁盘旋转时间,速度会非常快。

操作系统一般将内存和磁盘分割成固定大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点。并且可以利用预读特性,相邻的节点也能够被预先载入。

  • 支持范围查找

哈希表能以 O(1) 时间进行查找,但是失去了有序性:

无法用于排序与分组;
只支持精确查找,无法用于部分查找和范围查找。

MySQL索引

B+ Tree 索引

数据库中的B+Tree索引可以分为聚集索引(clustered index)和辅助索引(secondary index)。上面的B+Tree示例图在数据库中的实现即为聚集索引,聚集索引的B+Tree中的叶子节点存放的是整张表的行记录数据。辅助索引与聚集索引的区别在于辅助索引的叶子节点并不包含行记录的全部数据,而是存储相应行数据的聚集索引键,即主键。当通过辅助索引来查询数据时,InnoDB存储引擎会遍历辅助索引找到主键,然后再通过主键在聚集索引中找到完整的行记录数据。

聚集索引(主键索引)

按照每张表的主键构造一棵B+树,同时叶子节点中存放的即为整张表的行记录数据。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚集索引。
在这里插入图片描述
优点:
1、能够在叶子节点上直接找到数据。
2、对于主键的排序查找和范围查找速度非常快。

辅助索引
辅助索引的叶子节点的 data 域记录着主键的值,不包含行记录的所有数据。因此在使用辅助索引进行查找时,需要先查找到主键值,然后再到聚集索引中进行查找。每张表上可以有多个辅助索引。
在这里插入图片描述

哈希索引

哈希索引能以 O(1) 时间进行查找,但是失去了有序性:

  • 无法用于排序与分组;
  • 只支持精确查找,无法用于部分查找和范围查找。

哈希索引底层的数据结构是哈希表,在绝大部分需求为单条查询的时候,可以选择哈希索引。

InnoDB 存储引擎有一个特殊的功能叫自适应哈希索引,当某个索引值被使用的非常频繁时,会在B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。不能人为干预是否生成哈希索引。

全文索引

MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。
查找条件使用 MATCH AGAINST,而不是普通的 WHERE。
全文索引使用倒排索引实现,它记录着关键词到其所在文档的映射。
InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。

联合索引

对表上的多个列进行索引,mysql创建联合索引的规则是首先会对联合合索引的最左边的,也就是第一个字段col1的数据进行排序,在第一个字段的排序基础上,然后再对后面第二个字段col2进行排序。其实就相当于实现了类似 order by col1 col2这样一种排序规则。第二个索引可能是无序的。要想用到第二个索引,必须用第一个索引。

最左匹配原则:即最左优先,在检索数据时从联合索引的最左边开始匹配。
index(a,b,c) 仅a , ab , abc可使用索引。

覆盖索引

SQL只需要通过辅助索引就可以返回查询所需要的数据,而不需要查询聚集索引中的记录。
对于统计问题,使用辅助索引,减少IO。

对于频繁的查询优先考虑使用覆盖索引(索引优化)。

优点:

  • 索引通常远小于数据行的大小,只读取索引能大大减少数据访问量。
  • 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。
  • 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。

优化器选择不使用索引的情况

在范围查找、JOIN连接操作等情况下,有可能不使用辅助索引。

SELECT * FROM orderdetails WHERE orderid>1000 and orderid<2000;

orderid是辅助索引,但sql最终选择使用聚集索引。

用户要选取的信息是整行信息,而索引不能覆盖。通过辅助索引查到指定数据后,还需要再进行一次书签访问来查找整行数据信息。此时数据是无序的。为了减少随机IO,优化器选择使用聚集索引。

索引使用

Explain的使用

Explain用来查看SQL语句的执行计划,查看该SQL语句有没有使用上了索引,有没有做全表扫描等。
在这里插入图片描述

  • id: SELECT的查询序列号。例如查询中包含子查询,子查询序列号会加1.查询序列号越高,优先级越高。
  • select_type: 查询类型。简单查询或子查询或UNION等。
  • table: 所访问数据库的表名称
  • type: 对表的访问方式。

常用的类型有: ALL、index、range、 ref、eq_ref、const、system、NULL(从左到右,性能从差到好)

ALL:全表扫描
index: Full Index Scan,index与ALL区别为index类型只遍历索引树
range: 只检索给定范围的行,使用一个索引来选择行。
这种类型表示mysql会根据特定的算法快速查找到某个符合条件的索引,而不是会对索引中每一个数据都进行一 一的扫描判断
ref: 非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问。
eq_ref: 对于每个索引键,表中只有一条记录与之匹配
const:表示通过索引一次就找到了
NULL: MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。

  • possible_keys: 表示查询时,可能使用的索引
  • key:表示实际使用的索引
  • key_len:索引字段的长度
  • ref:显示索引的哪一列被使用了,如果可能的话,是一个常数。使用哪列或常数与key一起从表中选择行。
  • rows: 估算出结果集行数,表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数
  • Extra: 执行情况的描述和说明。Using index(仅通过索引就可以获取所需数据)/Using where/Using MRR/Using temporary等

MRR

根据辅助索引找到数据后,如果要查找的是全表数据,回表查询时变为无序,(辅助索引有序,对应主键索引未必有序),MRR将查询到的辅助索引键值放入缓存中,再根据RowID进行排序之后再去访问,减少磁盘随机IO。

ICP

首先根据索引查询记录,再根据WHERE条件来过滤记录;
ICP: 在取出索引的同时,判断是否可以进行WHERE条件的过滤;

索引失效

  • 对索引列进行运算导致索引失效;
SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;
  • 组合索引中,如果中间某个字段使用了范围条件,则右边的列索引失效;
  • like通配符必须放在索引列的右边,否则索引失效:建立翻转函数索引;
#在name上加索引 索引不会失效
SELECT name FROM staff WHERE name LIKE '%Alice%';
  • 使用不等于(!= 或者<>)不能使用索引;使用 is null 或者 is not null 也不能使用索引;
  • 如果条件中有or,即使其中有条件带索引也不会使用。要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引;
  • 搜索一个索引而在另一个索引上做order by,where A=a order by B,只使用A上的索引,因为查询只使用一个索引;
  • 如果列类型是字符串,要使用引号,如where A=‘China’,否则不使用索引;
  • 如果mysql估计使用全表扫描要比使用索引快,则不使用索引。

索引优化

单列索引

出现在 SELECT、UPDATE、DELETE 语句的 WHERE 从句中的列。
经常需要排序、分组和联合操作的字段。
尽量使用数据量少的索引:占用磁盘空间小。

多列索引

  • 在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。

例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引。

SELECT film_id, actor_ id FROM sakila.film_actor
WHERE actor_id = 1 AND film_id = 1;
  • 限制每张表上的索引数量。避免建立冗余索引和重复索引。

索引并不是越多越好!索引可以增加查询效率,但同样也会降低插入和更新的效率,甚至有些情况下会降低查询效率。创建和维护索引要耗费时间,索引需要占用物理空间。
因为优化器在选择如何优化查询时,会根据统一信息,对每一个可以用到的索引来进行评估,以生成出一个最好的执行计划,如果同时有很多个索引都可以用于查询,就会增加 MySQL 优化器生成执行计划的时间,同样会降低查询性能。

  • 尽量扩展现有索引:联合索引的查询效率比多个独立索引高。

索引列的顺序

  • 区分度最高的放在联合索引的最左侧(区分度=列中不同值的数量/列的总行数)
  • 尽量把字段长度小的列放在联合索引的最左侧(因为字段长度越小,一页能存储的数据量越大,IO 性能也就越好)
  • 使用最频繁的列放到联合索引的左侧

例如下面显示的结果中 customer_id 的区分度比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。

SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity,
COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity,
COUNT(*)
FROM payment;
staff_id_selectivity: 0.0001
customer_id_selectivity: 0.0373
COUNT(*): 16049

前缀索引

对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。(提高执行效率,占用磁盘空间更小)

前缀长度的选取需要根据索引选择性来确定。

为什么 Mysql 用 B + 树做索引而不用 B 树或红黑树?

Mysql 通过磁盘IO次数衡量查询效率。B+ 树是在B树的基础上进行改造,只有叶节点存放数据,其余节点用来索引,同时叶子结点之间还加了指针形成链表;而B- 树是每个索引节点都会有 Data 域。

为什么不用B树?

  • B - 树的每个节点都有 data 域(指针),增大了节点大小,即增加了磁盘 IO 次数(磁盘 IO 一次读出的数据量大小是固定的,单个数据变大,每次读出的就少,IO 次数增多,更耗时)。而 B + 树除了叶子节点其它节点并不存储数据,节点小,磁盘 IO 次数就少。
  • B + 树所有的 Data 域在叶子节点,一般来说都会进行一个优化,就是用指针将所有的叶子节点串起来。这样遍历叶子节点就能获得全部数据,这样就能进行区间访问。在数据库中基于范围的查询是非常频繁的,而 B 树不支持这样的遍历操作。
  • B+树节点更小,一次IO读入的节点数更多;数据都在叶子节点中,遍历和区间查询、访问性能大幅提高;查询效率稳定。

B树的优点

B树的每一个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速。在B树中,要查找的值恰好处在一个非叶子节点时,查找到该节点就会成功并结束查询。在B+树中,无论查找成功与否,都走了一条从根到叶子节点的路径。

B树 比 B+树 的空间冗余小一些(Key 少了一些)。

为什么不用AVL树、红黑树?

AVL树和红黑树基本都是存储在内存中才会使用的数据结构。在大规模数据存储的时候,红黑树往往出现由于树的深度过大而造成磁盘 IO 读写过于频繁,进而导致效率低下的情况。

磁盘查找存取的次数往往由树的高度所决定,所以我们需要通过某种较好的树结构尽量减少树的高度。B 树可以有多个子女,从几十到上千,可以降低树的高度,且一个节点中可以存放很多的key(个数由树阶决定)。相同数量的key在B树中生成的节点要远远少于二叉树中的节点,相差的节点数量就等同于磁盘IO的次数。这样到达一定数量后,性能的差异就显现出来了。

数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次 I/O 就可以完全载入。为了达到这个目的,在实际实现 B-Tree 还需要使用如下技巧:每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,就实现了一个 node 只需一次 I/O。

B+树的树高比AVL树、红黑树低,IO次数少。

为什么mysql用B+树而不是hash来存储索引?

MySQL中存储索引用到的数据结构是B+树,B+树的查询时间跟树的高度有关,是log(n),如果用hash存储,那么查询时间是O(1)。既然hash比B+树更快,为什么mysql用B+树来存储索引呢?

哈希是通过hash函数计算出一个hash值的,存在哈希冲突的情况,另外哈希也不支持部分索引查询以及范围查找。但是哈希的优点就是查找的时间复杂度是O(1),那么什么情况下可以使用hash索引呢?就是查询条件不会变,而且没有部分查询和范围查询的时候。

  • 从内存角度上说,数据库中的索引一般是在磁盘上,数据量大的情况可能无法一次性装入内存,B+树的设计可以允许数据分批加载。
  • 从业务场景上说,如果只选择一个数据那确实是hash更快,但是数据库中经常会选中多条这时候由于B+树索引有序,并且又有链表相连,它的查询效率比hash就快很多了。

为什么不用红黑树或者二叉排序树?

树的查询时间跟树的高度有关,B+树是一棵多路搜索树可以降低树的高度,提高查找效率。

既然增加树的路数可以降低树的高度,那么无限增加树的路数是不是可以有最优的查找效率?

这样会形成一个有序数组,文件系统和数据库的索引都是存在硬盘上的,并且如果数据量大的话,不一定能一次性加载到内存中。有序数组没法一次性加载进内存,这时候B+树的多路存储威力就出来了,可以每次加载B+树的一个结点,然后一步步往下找。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值