SQL进阶理论篇(九):为什么不存在完美的索引

文章目录

  • 简介
  • 索引片和过滤因子
  • 如何通过宽表避免回表
  • 什么是过滤因子
  • 理想索引设计:三星索引
  • 为什么很难存在理想的索引设计?
  • 参考文献

简介

本节将主要介绍以下部分:

  • 什么是索引片,什么是过滤因子?
  • 设计索引的时候,可以遵循哪些原则
  • 为什么理想的索引很难应用在实际工作中?

索引片和过滤因子

索引片就是SQL查询语句在执行时需要扫描的一个索引片段,我们会根据索引片中包含的匹配列的数量的不同,将索引分为窄索引和宽索引。其中窄索引一般指包含索引列数为1或者2,宽索引一般指列数大于2。

如果索引片越宽,那么可能需要顺序扫描的索引页就越多;如果索引片越窄,在一定程度上就可以减少索引访问的开销。

比如,在product_comment表中,comment_id为主键,我们可以设置(user_id)为窄索引,也可以设置其他所有字段(user_id, product_id,comment_text)为宽索引。

如图:

SQL进阶理论篇(九):为什么不存在完美的索引_第1张图片

需要说明的是,每个非聚集索引(二级索引)里都会保存主键值,然后通过主键值,去磁盘上回表来查找相应的记录行(二次操作,所以叫做二级索引)。因此每个索引都相当于包括了主键

所以我们在声明窄索引(user_id)的时候,其实就相当于声明了(comment_id,user_id)。我们声明宽索引(user_id, product_id,comment_text)时,就相当于声明了(comment_id,user_id, product_id,comment_text),覆盖了全表字段。

如何通过宽表避免回表

宽索引需要顺序扫描的索引页很多,但也是有好处的,在多数情况下,它可以不需要回表,直接从索引树里拿数据就可以(需要的字段都是索引)。

回表指的就是,数据库在根据索引找到了主键之后,还需要通过主键再次到数据表中去读取对应记录行的过程。

教程里举了个例子,来讲解使用不同的索引片来运行相同查询时的时间差异。

比如,先以窄索引(user_id)来执行下面语句:

SELECT comment_id, product_id, comment_text, user_id FROM product_comment WHERE user_id between 100001 and 100100

返回110条记录,耗时0.062s。

接着我们再以宽索引(user_id, product_id, comment_text)运行上面语句,结果相同,耗时为0.043s。

可以看到,查询效率会有一些提升。这就是因为select中需要的列都在宽索引里,数据库直接扫描索引树就可以,不需要再回表了,所以一定程度上提升了SQL查询的效率。

什么是过滤因子

在设计索引片的时候,我们还需要考虑一个辅助因素,就是过滤因子

过滤因子,描述了谓词的选择性。

什么是谓词呢?

在where条件语句中,每一个条件都称为一个谓词。因此谓词选择性,实际上就等于满足这个条件列的记录数除以总记录数的值。可以理解成满足条件的记录数比例。

比如说我们有一个player球员表,其中有team_id,height、name、gender等字段,然后gender的取值都是male。

接下来我们评估下这些过滤因子的筛选能力:

SQL进阶理论篇(九):为什么不存在完美的索引_第2张图片

可以看到,gender和team_id都不是一个好的过滤因子,单值比例有些过于高了。相比之下,过滤因子筛选能力最强的,是name字段。

那如果我们创建一个联合过滤条件,如(height, team_id),它的过滤能力如图:

SQL进阶理论篇(九):为什么不存在完美的索引_第3张图片

可以看到联合过滤因子的过滤性是很强的。

不过需要注意,在联合的时候,各组成条件的关联性应该尽量互相独立,如果列和列之间具有相关性,那么创造出来的联合过滤因子,过滤效果就会差很多。

因此,过滤因子实际上决定了索引片的大小,即索引片中的记录数。过滤因子的条件过滤能力越强,满足条件的记录数就越少,索引片也就越小。

理想索引设计:三星索引

在索引的设计里,确实存在一种规范,叫做三星索引规范,其在索引设计中的地位相当于3NF在数据表设计中的地位。

三星索引具体是指:

  • 在where条件语句中,找到所有等值谓词中的条件列,将它们作为索引片中的开始列;
  • 将group by和order by的列也加入索引;
  • 将select字段中剩余的列加入索引片。

如此下来,我们的索引片基本会变成一个宽索引,近乎涵盖了所有能添加的相关列。那么对于一条查询来说,这样做的效率是最高的码?

一般来讲,是的。

首先,将where中的等值谓词加入索引片,借助索引进行过滤,效率自然是高的。

其次,将group by和order by中的列也加入索引,可以有效避免进行file sort排序,因为创建了索引就会按照索引顺序来存储数据,二次分组或者排序的时候就会提升效率。

最后,select中列加入索引。好处更明显了,就是避免回表现象。所有的列都在索引树里了,还回表去读什么记录行?节省了IO,自然就快了。

为什么很难存在理想的索引设计?

三星索引设计这么高效,那是不是大家都用三星索引就可以了?

那当然不是,三星索引的缺点也很明显。主要有以下几点:

  • 索引片太宽了。一个索引片十几二十列,那么单个页能放的索引数据就少了,要读取相同数量的索引数据的话,就要去读更多的页,在极端情况下,很难说效率还会不会高。
  • 如果数据量过大,比如说成百上千万行,那么存储它们的索引,也需要占用很大的磁盘,而且这么多索引,带给缓冲池空间的压力也很大。
  • 索引维护的成本很高。当新数据进来的时候,就需要重构索引,当成百上千的数据进来呢?这个时间和计算资源的消耗是很大的。

因此,我们实际使用的时候,还是需要权衡的,而且大多数情况下,我们还是会像反范式设计一样,反三星式设计。

  • 单个表,索引不宜过多,否则增加/修改数据时,对索引的修改会造成额外的消耗。
  • 定期检查索引使用情况,对于使用频率低的索引,可以及时删除。
  • 控制索引片中的索引列数量。通常我们会把where中的条件列加入索引,而select里的不会。
  • 尽量使用数值类型代替字符类型来构建索引,避免使用字符类型做主键,对字符字段最好只建前缀索引。在 MySQL InnoDB 中,系统默认单个索引长度最大为 767 bytes,如果单列索引长度超过了这个限制,就会取前缀索引,也就是取前 255 字符。这实际上也是告诉我们,字符列会占用较大的空间

看到一条很有趣的评论,他说"所谓的三星索引,其实就是面向查询建了个表"。哈哈哈哈哈哈,我觉得他说的很有道理。

参考文献

  1. 29丨为什么没有理想的索引?

你可能感兴趣的:(#,SQL基础,sql,数据库)