跳至主要內容

1、MySQL索引

KindBrave大约 10 分钟

1.索引的介绍

索引就是为了快速查找内容设立的,可以参考新华字典里的目录,如果说直接去后面翻来找字的话,那么时间会非常长,但是如果通过前面的目录进行拼音查找或者部首查找,那么效率就非常高了,实际上这里的拼音和部首,就是两种索引。

2.索引的优缺点

优点:

  • 可以让查找效率变得很高
  • 通过建立唯一索引,可以让行唯一

缺点:

  • 建立和修改索引,都需要大量的时间。因为每次对数据内容进行更变,都需要更变索引的内容
  • 索引是一个文件,需要存放到磁盘中,所以会占用一定的空间

3.索引的底层数据结构

3.1 hash

哈希表是一种键值对存储,我们可以把索引放到key中。对于散列函数比较优秀的哈希表来说,基本上不会产生冲突,如果产生了冲突,那就通过链表的方式来解决。正常来说,哈希表可以实现O(1)时间的查找。

缺点:

  • 哈希表单查找确实很快,但是进行范围查找,比如a>100 and a<200,那就不太合适了,对于哈希表结构来说,每个值都需要进行一次查找

3.2 二叉查找树

二叉查找树是一个二叉树,但是它节点有一些特点:每个节点的左孩子都比自己小,每个节点的右孩子都比自己大

这样的话,如果查找一个值,平均只需要O(logn)的时间就可以完成,但是二叉查找树可能会退化到一条链,这是进行查找和普通链表没什么区别了,会退化成O(n)时间

3.3 平衡二叉树

为了解决上面二叉查找树会退化为一条链的情况,平衡二叉树做了一些工作。

平衡二叉树规定,对于每个节点来说,左孩子和右孩子的高度之差不能大于1,如果大于1了,就要进行多次左旋或者右旋,直到高度之差满足条件。

缺点:

平衡二叉树很好地解决了查找退化的问题,但是因为每次插入或者删除,都可能会进行多次的左旋或者右旋,这样消耗的时间比较大

3.4 红黑树

接下来就是红黑树,上面说的平衡二叉树每次都可能进行多次的左旋或者右旋,而红黑树每次都只进行一次左旋或者右旋。

一个红黑树有以下5个特点:

  • 每个节点不是红色就是黑色的
  • 根节点一定是黑色的
  • 叶子节点是黑色的空节点
  • 一个节点是红色的话,那么它的孩子节点一定是黑色的,反过来不一定(不能连着两个红)
  • 从根节点到叶子节点,每条路径上的黑色节点数量一定是相同的

红黑树因为每次的左旋或者右旋都只进行一次,所以插入和删除的效率比平衡二叉树来说是有所提升的

但是红黑树并不是完全保证平衡的,也就是对于一个节点来说,它的左孩子和右孩子的高度可能差值会大一些,这样查找效率可能会有所下降,树高可能会高一些,IO操作就会多一些,这也是MySQL没有选择红黑树的原因(B+树是矮胖的)

红黑树是如何保证平衡的?

旋转和染色。

3.5 B树/B+树

B树叫做多路平衡查找树,B+树是B树的一个改进

B树和B+树的区别如下:

  • B树所有位置都可以存放key和data,B+树只有叶子节点存放key和data,其他位置只存放key
  • B树中一个关键字只能出现一次,B+树中可能出现多次
  • B树的叶子节点是相互独立的,B+树的叶子节点之间是通过一个链连在一起的
  • B树进行查找只需要查到key就可以了,因为节点中既有key又有value;而B+树必须要查到叶子节点
  • 对于范围查找来说,B树需要查找到左范围,然后通过中序遍历,直到找到右范围;而B+树通过叶子节点的链表即可

这样来说,B+树有很多优势:

  • 查找更稳定,每个都需要到达叶子节点
  • 范围查找更快捷

所以,MySQL的InnoDB和MyISAM(Maiˈzæm)都使用了B+树。

有关B+树的一些补充

B+树是有阶的概念的,对于一个m阶的B+树,那么它的孩子数量最多就是m个。

对于B+树的一个节点来说,它内部存放的可能是这样:20 30 40...,小于20的会走一个孩子,20-30的会走一个孩子,依次。

对于查询时间复杂度的话,基本上就是logn的。

4.主键索引和二级索引

主键索引就是为主键建立的索引,对于InnoDB来说,主键索引中的key是主键,data就是这一行的内容了,对于MyISAM来说,主键索引中的data也是指向数据的一个值

对于InnoDB来说,如果没有指定主键所以,它会去寻找唯一非空索引,如果有,那么就把它当作主键索引,否则就创建一个6bit的主键索引

对于二级索引来说,其data的值不是数据,而是主键的值,这样通过二级索引来检索数据,实际上是需要回表的,再根据主键索引查找到数据

在InnoDB中,除了主键索引都是二级索引,包括

  • 普通索引
  • 唯一索引

5.聚簇索引和非聚簇索引

5.1 聚簇索引

所谓聚簇索引,就是指的索引和数据是在一起的,比如InnoDB中的主键索引。而MyISAM主键索引也是非聚簇索引,主键索引中的data也不是这一行的数据,而是指向了数据的位置

下面记一下,忘记了

优点:

  • 对于排序查找和范围查找很方便
  • 聚簇查找不需要进行回表查找,因为data中就是这一行的信息了。查找非常快,因为聚簇查找不需要进行回表操作,这样会减少一次IO操作

缺点:

  • 依赖于有序数据,如果数据是有序的,那么建立和搜索索引都会非常顺序,如果是无序的,比如UUID什么的,那么索引之间的比较就会很耗时
  • 更新代价大。因为聚簇索引数据是放到data中的,这样的话更新索引也需要处理这些数据,耗时较大

5.2 非聚簇索引

非聚簇索引就是索引中的data存放的不是真正的数据,而是数据的指针或者主键的值。

优点:

  • 更新消耗小,因为非聚簇索引中没有真正的值

缺点:

  • 依赖有序数据,同上
  • 查找需要进行回表操作,数据查询比较慢

非聚簇索引一定会回表查询吗?

不一定,如果查的列恰好是有索引的,那对于InnoDB来说,就不需要进行回表了。

对于MyISAM来说,如果查的列是主键,那么其实也不需要进行回表。

这种情况就是覆盖索引了。

6.覆盖索引和联合索引

所谓覆盖索引,就是准备查找的列恰好都是有索引的列,这样查找的时候就不需要进行回表操作。

所谓联合索引,就是把多列合在一起共同作为一个索引。

6.1 最左前缀匹配原则

忘了,看看

最左前缀匹配原则,就是在使用联合索引时,根据联合索引中从左到右的顺序,先过滤掉一些不符合的数据再进行下面的匹配,这样效率会高一些。所以在进行联合索引设计时,应该把一些过滤效果好的列放到左边。

过滤什么时候会停止?> <都会,>= <= between like不会

所以对于联合索引 (a, b)来说,如果查询:

select a, b where a=1 and b=2是可以走索引的,但是查询:

select b 就是不走索引的,

select a, b, c where a = 1 and b > 2 and c=1,那么c就是走不了索引的

7.索引下推

8.正确使用索引的建议,感觉比较重要

8.1 选择合适的列作为索引

  • 不应该把可空的列作为索引,因为索引很难优化NULL值
  • 频繁查找的列作为索引
  • 频繁作为条件查询的列作为索引
  • 频繁作为连接字段的列作为索引
  • 频繁更新的列不要作为索引,因为更新也需要更新索引,所以比较耗时

什么情况可以加索引

  • 有唯一性的字段
  • order by/group by这种可以加索引,因为索引已经拍好了
  • 经常被where/select使用的

什么情况下不应该加索引

  • 表中数据较少
  • 经常被更新的列
  • where/order by/group by用不上的字段
  • 重复值较多的列

8.2 每张表上的索引不宜过多

每张表上的索引最多不要大于5个,索引虽然能够加快查找,但是也需要消耗时间来维护,并且也需要占用一定的存储空间,如果索引过多,那么可能会适得其反

8.3 删除不常用的索引

8.4 尽可能地使用联合索引

我们应该尽可能地使用联合索引而不是单列索引,因为索引也是要存储的,我们可以把索引联合起来,这样只需要存一个B+树

8.5 对于字符串索引,要使用前缀索引

因为前缀索引占用空间更小,查询速度更快

8.6 要避免索引失效

这个另说,见另一篇

8.7 要避免索引重复

如果已经建立了联合索引,那么单列索引就不需要了,需要删除

有关索引的使用,可以见下面:

mysql> create index ai on t (a);
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> create index bi on t (b);
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> insert into t values (1, 1, 'ab', 'cd');
Query OK, 1 row affected (0.01 sec)

mysql> insert into t values (2,2,'abc','def');
Query OK, 1 row affected (0.01 sec)

mysql> select * from t;
+---+------+------+------+
| k | a    | b    | c    |
+---+------+------+------+
| 1 |    1 | ab   | cd   |
| 2 |    2 | abc  | def  |
+---+------+------+------+
2 rows in set (0.00 sec)

mysql> explain select * from t;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | t     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select a from t;
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t     | NULL       | index | NULL          | ai   | 5       | NULL |    2 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select a from t where b like 'a%';
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | t     | NULL       | range | bi            | bi   | 403     | NULL |    2 |   100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

注意到type从ALL到index到range