本节将主要介绍以下部分:
索引片就是SQL查询语句在执行时需要扫描的一个索引片段,我们会根据索引片中包含的匹配列的数量的不同,将索引分为窄索引和宽索引。其中窄索引一般指包含索引列数为1或者2,宽索引一般指列数大于2。
如果索引片越宽,那么可能需要顺序扫描的索引页就越多;如果索引片越窄,在一定程度上就可以减少索引访问的开销。
比如,在product_comment表中,comment_id为主键,我们可以设置(user_id)为窄索引,也可以设置其他所有字段(user_id, product_id,comment_text)为宽索引。
如图:
需要说明的是,每个非聚集索引(二级索引)里都会保存主键值,然后通过主键值,去磁盘上回表来查找相应的记录行(二次操作,所以叫做二级索引)。因此每个索引都相当于包括了主键。
所以我们在声明窄索引(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。
接下来我们评估下这些过滤因子的筛选能力:
可以看到,gender和team_id都不是一个好的过滤因子,单值比例有些过于高了。相比之下,过滤因子筛选能力最强的,是name字段。
那如果我们创建一个联合过滤条件,如(height, team_id),它的过滤能力如图:
可以看到联合过滤因子的过滤性是很强的。
不过需要注意,在联合的时候,各组成条件的关联性应该尽量互相独立,如果列和列之间具有相关性,那么创造出来的联合过滤因子,过滤效果就会差很多。
因此,过滤因子实际上决定了索引片的大小,即索引片中的记录数。过滤因子的条件过滤能力越强,满足条件的记录数就越少,索引片也就越小。
在索引的设计里,确实存在一种规范,叫做三星索引规范,其在索引设计中的地位相当于3NF在数据表设计中的地位。
三星索引具体是指:
如此下来,我们的索引片基本会变成一个宽索引,近乎涵盖了所有能添加的相关列。那么对于一条查询来说,这样做的效率是最高的码?
一般来讲,是的。
首先,将where中的等值谓词加入索引片,借助索引进行过滤,效率自然是高的。
其次,将group by和order by中的列也加入索引,可以有效避免进行file sort排序,因为创建了索引就会按照索引顺序来存储数据,二次分组或者排序的时候就会提升效率。
最后,select中列加入索引。好处更明显了,就是避免回表现象。所有的列都在索引树里了,还回表去读什么记录行?节省了IO,自然就快了。
三星索引设计这么高效,那是不是大家都用三星索引就可以了?
那当然不是,三星索引的缺点也很明显。主要有以下几点:
因此,我们实际使用的时候,还是需要权衡的,而且大多数情况下,我们还是会像反范式设计一样,反三星式设计。
看到一条很有趣的评论,他说"所谓的三星索引,其实就是面向查询建了个表"。哈哈哈哈哈哈,我觉得他说的很有道理。