MySQL 索引

196 阅读10分钟

什么是索引?

索引是一种帮助我们快速查找数据的数据结构,它就像是一个字典的目录。要查哪个字,我们一般就要先通过字典的目录来找到字的位置再去查询,索引起到的作用与目录类似。

索引的结构

MySQL的默认InnoDB引擎是使用的B+树。为什么使用B+树而不是别的?分析一下便知。

1、数组结构

数组结构支持随机查询,查询效率也很高,而且还支持范围查询。它的缺点就和其他地方数组的缺点一样,在增删数据方面效果很差。比如有1万条数据,我如果在中间插入或删除一条数据,变动位置后面的数据都要相应的进行移动。1万条数据可能还行,但如果有频繁的增删或数据量极大的情况,数组的效果就很差了。
可以用在历史记录这种数据库,比如去年的交易记录什么的。

2、哈希

哈希的存储要先进行哈希运算,计算出值后放入对应的位置。这样可以会有发生冲突的情况,这种情况可以通过链表的形式解决。哈希结构在查找单条数据的时候效率非常快,但不支持范围查询,如果要范围查询只能遍历整个数据库,花销极大。
可以用在Redis这种kv存储数据的中间件中。

3、二叉树

3.1 普通二叉树

普通二叉树可以通过搜索树的形式使节点是有序的,所以可以实现范围查询,而且二叉树结构的增删改查效率稳定。但二叉树会有可能变成歪脖子树,即所有节点都偏向一边,这样子就和数组一样了。

3.2 平衡二叉树

平衡二叉树解决了二叉树的不平衡问题,但又来了新的问题,就是平衡二叉树的定义很严格,所以在增删改查的时候很可能使树结构重构。试想一下,我修改了一下数据,整个数据库的索引结构就要重构一次,如果我要批量插入数据该怎么办。

3.3红黑树

红黑树是是一种自平衡二叉查找树,不会出现歪脖子树的情况也不会像平衡二叉树那样经常重构。使用红黑树看着是解决了之前的问题,但InnoDB最后没有选择使用红黑树,为什么?

还存在什么问题?

首先是读磁盘的次数,通过树结构查找大部分都会是树的高度就是磁盘IO的次数。数据量大的情况下也会是不小的开支。其次是MySQL每次IO读磁盘的时候是按数据页来读的,一次只读一个数据页,读当前数据页的内容,没有目标数据则根据结构去IO下一个数据页,而红黑树的一个节点的大小远远小于一个数据页,这就造成了数据页的空间利用率低。由上两个方面的考虑,我们就希望树的高度能变矮,同时一个树节点可以尽可能多的存放数据。所以就有了B树。 二叉树的范围查找只能从根节点开始遍历,效率不高。

4、B树

4.1 B树是一种多叉平衡查找树,它的特点如下:

  1. B树的节点中存储着多个元素,每个内节点有多个分叉。
  2. 节点中的元素包含键值和数据,节点中的键值从大到小排列。在所有的节点都储存数据。
  3. 父节点当中的元素不会出现在子节点中。
  4. 所有的叶子结点都位于同一层,叶节点具有相同的深度。

通过B树的特点我们也知道了B树是一种矮胖的树,B树一个节点的最佳大小也是一个数据页的倍数(这样就不会有空间的浪费)。问题都解决了。但有了新问题:范围查询的时候当前节点可能不全,需要一级一级地返回上一节点继续查询,影响效率;随着数据的增多,索引也会变多,这样一个节点存储的数据就变少了,B树就不得不提升高度来存储数据,IO的次数就变多了。而且范围查询要从根节点重复遍历,效率低下。

4.2 B+树

B+树是B树的升级版,特性与B树相同,唯一不同的点是:B+树所有的叶子结点都位于同一层,叶节点具有相同的深度。叶子结点呈双向链表结构,范围查询速度快;只在叶子结点存数据,理论上B+树比B树要矮。

B+树优化:

  • 单点查询:单独查询一个节点b树最快可以是o(1)级别的,但使用b树查询的差距会比较大。B+树所有数据放在叶子结点,所以树的高度会比b树更加矮一点,IO次数也就会更少。
  • 范围查询,b树要实现范围查询要进行树的递归查询才能实现,一次次地从根节点出发,效率低,而b+树的数据都存储在叶子结点,并且叶子结点间通过链表连接起来,对数据查询十分有帮助。
  • 插入删除:b+树有冗余节点而b树没有,所以在增删节点的时候b树可能会发生结构变化而b+树不会。

在内存使用上,使用B+树可以将非叶子结点(索引数据)保存到内存上,这样可以提高查询是速度。而使用B树的话非叶子节点既存索引又存数据导致很有可能存不下所有的非叶子节点。这也就导致了不可避免的磁盘io,所以从这方面考虑的话使用B+树会比使用B树更能节省时间。

综上,InnoDB选择了B+树作为自己的索引结构。

索引分类

1、按结构分类:

  • B+树
  • hash

2、物理存储的角度

  • 聚簇索引
  • 非聚簇索引

InnoDB表的索引按照叶子节点存储的是否为完整表数据分为聚簇索引和非聚簇索引。

聚簇索引的叶子节点就是数据节点,也就是说索引和数据行在一起;反之,如果叶子节点没有存储数据行,那么就是非聚簇索引。

辅助索引、二级索引都属于非聚簇索引。

3、索引字段逻辑特性角度

  1. 普通索引:最基本的索引,它没有任何限制。
  2. 唯一索引:与普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须一。
  3. 主键索引:是一种特殊的唯一索引,不允许有空值。一般是在建表的时候指定了主键,就会创建主键索引
  4. 联合索引:对多个字段同时建立的索引,比如(a,b,c),有顺序要求,如(a,b,c)和(a,c,b)不是同一个索引。
  5. 全文索引:FULLTEXT索引用于全文搜索。只有InnoDB和 MyISAM存储引擎支持 FULLTEXT索引和仅适用于 CHAR, VARCHAR和 TEXT列。

索引相关的术语

1、回表

所谓的回表查询,是指先定位主键值,再定位行记录,性能上较之直接查询索引树定位行记录更慢。

2、索引覆盖

覆盖索引是指,查询的数据列只用从索引中就能够取得,不必从数据表中读取。换句话说一个索引覆盖所有需要查询的字段的值,无需回表查询,速度更快。

3、最左前缀匹配原则

B+ 树的数据项是复合的数据结构/联合索引的时候(如:(a,b,c)),B+ 树是按照从左到右的顺序来建立搜索树的。即B+树会优先比较a来寻找,如果a值相同则再依次通过b,c来查询。同时由于有(a,b,c)的联合索引,我们可以省略构建(a),(a,b)索引。但单独(b)或(b,c)则不行。

索引的使用

索引虽然可以提升我们的查询效率,但索引带来的也不止有好处,也有一定的坏处。

  1. 索引的创建要有物理空间来保存,所以索引越多消耗的资源也就越多
  2. 创建和维护索引需要消耗时间
  3. 每当表要进行增删改操作的时候,索引就要进行对应的修改,B+树为了维护索引就会消耗时间。

所以我们要分场合来建立索引。

什么情况下适合用索引

  1. 索引有唯一性的限制适合使用索引
  2. 经常使用where查询语句的适合使用索引,这样可以提升整张表的查询效率,如果有多个字段可以考虑建立联合索引。
  3. 经常使用 order by和 group by的字段,因为索引本身就是排好序的,这样做可以减少排序

不用建立索引的场合

  1. group by和 order by中用不到的字段不用建立索引,因为索引是为了快速定位,而这些字段用不到,我们建立索引也是浪费空间。
  2. 有大量重复字段的不用建立索引,比如人员表中的性别,只有男女,为这个字段建立索引只会筛选出一半。
  3. 数据太少可以不用建立索引。
  4. 经常更新的字段可以不建立索引,因为该字段经常发生变化,而b+树要维护这个字段的索引就要进行变更,这样也会导致性能变化。

使用索引时的优化

前缀索引

前缀索引就是使用某个字段中字符串的前几个字符建立索引,使用前缀索引是为了减小索引字段大小,可以增加一个索引页中存储的索引值,有效提高索引的查询速度。在一些大字符串的字段作为索引时,使用前缀索引可以帮助我们减小索引项的大小。 局限:

  1. order by不能适用
  2. 不能把前缀引用作为索引覆盖。

使用索引覆盖优化

通过二级索引就能找到我们需要查询的内容,就不需要从二级索引再跳到主键索引上,可以避免回表操作。 所以,使用覆盖索引的好处就是,不需要查询出包含整行记录的所有信息,也就减少了大量的 I/O 操作。

主键索引最好是自增的

因为索引是通过b+树进行存储的,当主键索引是自增的时候数据就会按照主键索引的值按序排列,这样添加索引的时候数据也是按序添加不需要移动旧数据,这样当一页数据满的时候直接开辟新的一页就行了,不需要将页分裂。页分裂会导致内存碎片的问题。

索引最好设置为 NOT NULL

因为

  1. 当字段有null时,就会导致优化器在做索引选择的时候更加复杂,更加难以优化,因为可为 NULL 的列会使索引、索引统计和值比较都更复杂。
  2. null没有意义,但会占用存储空间。

要防止索引失效