高性能的索引创建策略
正确地创建和使用索引是实现高性能查询的基础。前面我们已经了解了索引相关的数据结构,各种类型的索引及其对应的优缺点。现在我们一起来看看如何真正地发挥这些索引的优势。
索引列的类型尽量小
我们在定义表结构的时候要显式的指定列的类型,以整数类型为例,有TTNYINT、NEDUMNT、INT、BIGTNT这么几种,它们占用的存储空间依次递增,我们这里所说的类型大小指的就是该类型表示的数据范围的大小。能表示的整数范围当然也是依次递增,如果我们想要对某个整数列建立索引的话,在表示的整数范围允许的情况下,尽量让索引列使用较小的类型,比如我们能使用INT就不要使用BIGINT,能使用NEDIUMINT就不要使用INT,这是因为:
- 数据类型越小,在查询时进行的比较操作越快(CPU层次)
- 数据类型越小,索引占用的存储空间就越少,在一个数据页内就可以放下更多的记录,从而减少磁盘/0带来的性能损耗,也就意味着可以把更多的数据页缓存在内存中,从而加快读写效率。
这个建议对于表的主键来说更加适用,因为不仅是聚簇索引中会存储主键值,其他所有的二级索引的节点处都会存储一份记录的主键值,如果主键适用更小的数据类型,也就意味着节省更多的存储空间和更高效的I/0。
利用索引选择性和前缀索引
索引的选择性/离散性
创建索引应该选择选择性/离散性高的列。索引的选择性/离散性是指,不重复的索引值(也称为基数,cardinality)和数据表的记录总数(N)的比值,范围从1/N到1之间。索引的选择性越高则查询效率越高,因为选择性高的索引可以让MySQL在查找时过滤掉更多的行。唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
怎么算索引的选择性/离散性?比如order_exp这个表:
select COUNT(DISTINCT order_no)/count() cnt from order_exp;
select COUNT(DISTINCT order_status)/count() cnt from order_exp;
很明显,order_no列上的索引就比order_status列上的索引的选择性就要好,原因很简
单,因为order_status列中的值只有-1,0,1三种。
前缀索引
有时候需要索引很长的字符列,这会让索引变得大且慢。一个策略是前面提到过的模拟哈希索引。
模拟哈希索引:
order_exp表中order_note字段很长,想把它作为一个索引,我们可以增加一个order_not_hash字段来存储order_note的哈希值,然后在order_not_hash上建立索引,相对于之前的索引速度会有明显提升,一个是对完整的 order_note做索引,而后者则是用整数哈希值做索引,显然数字的比较比字符串的匹配要高效得多。
但是缺陷也很明显:
1、需要额外维护order_not_hash字段;
2、哈希算法的选择决定了哈希冲突的概率,不良的哈希算法会导致重复值很多;
3、不支持范围查找
只为用于搜索、排序或分组的列创建索引
也就是说,只为出现在WHERE 子句中的列、连接子句中的连接列创建索引,而出现在查询列表中的列一般就没必要建立索引了,除非是需要使用覆盖索引。又或者为出现在ORDER BY或GROUP BY子句中的列创建索引,这句话什么意思呢?比如:
SELECT * FROM order_exp ORDER BY insert_time, order_status,expire_time;
查询的结果集需要先按照insert_time值排序,如果记录的insert_time值相同,则需要按照order_status来排序,如果order_status的值相同,则需要按照expire_time排序。回顾一下联合索引的存储结构,u_idx_day_status索引本身就是按照上述规则排好序的,所以直接从索引中提取数据,然后进行回表操作取出该索引中不包含的列就好了。当然ORDER BY的子句后边的列的顺序也必须按照索引列的顺序给出,如果给出ORDER BY order_status,expire_time, insert_time的顺序,那也是用不了B+树索引的
合理设计多列索引
多列索引的列顺序至关重要。对于如何选择索引的列顺序有一个经验法则:将选择性最高的列放到索引最前列。当不需要考虑排序和分组时,将选择性最高的列放在前面通常是很好的。这时候索引的作用只是用于优化WHERE条件的查找。在这种情况下,这样设计的索引确实能够最快地过滤出需要的行,对于在WHERE子句中只使用了索引部分前缀列的查询来说选择性也更高。
尽可能设计三星索引
三星索引概念
索引将相关的记录放到一起则获得一星;
如果索引中的数据顺序和查找中的排列顺序一致则获得二星;
如果索引中的列包含了查询中需要的全部列则获得三星。
二星(排序星):
在满足一星的情况下,当查询需要排序,group by、 order by,如果查询所需的顺序与索引是一致的(索引本身是有序的),是不是就可以不用再另外排序了,一般来说排序可是影响性能的关键因素。
三星(宽索引星):
在满足了二星的情况下,如果索引中所包含了这个查询所需的所有列(包括 where 子句和 select 子句中所需的列,也就是覆盖索引),这样一来,查询就不再需要回表了,减少了查询的步骤和IO请求次数,性能几乎可以提升一倍。一星按照原文稍微有点难以理解,其实它的意思就是:如果一个查询相关的索引行是相邻的或者至少相距足够靠近的话,必须扫描的索引片宽度就会缩至最短,也就是说,让索引片尽量变窄,也就是我们所说的索引的扫描范围越小越好。这三颗星,哪颗最重要?第三颗星。因为将一个列排除在索引之外可能会导致很多磁盘随机读(回表操作)。第一和第二颗星重要性差不多,可以理解为第三颗星比重是50%,
第一颗星为27%,第二颗星为23%,所以在大部分的情况下,会先考虑第一颗星,但会根据业务情况调整这两颗星的优先度。