MySQL索引使用细节
这里不介绍MySQL索引是什么,仅总结索引使用的一些注意事项
索引的代价
我们知道了B+
树的索引原理之后,也知道了表中的数据都是在聚簇索引上的,同时就是一颗B+
树。所有之后按其他列建立的索引都是二级索引也叫非聚簇索引。
奇迹和魔法可不是免费的
额外的索引当然是有代价的,就像正常插入数据到聚簇索引中需要调整B+
树一样,二级索引也有一模一样的维护代价。那么毫无疑问多余的索引存在:
-
空间代价
B+
树的一个节点在内存中体现为一个页,一页的大小是16KB
,如果索引很多,内存中就会有更多的索引页。 -
时间代价
如果一个表频繁的被修改,那么每次操作都要同步去修改
B+
树中的索引值,索引越多需要同时维护的B+
树就越多,这显然是一笔不小的代价
所以索引的建立不是越多越好,索引的选择和使用更像是不断权衡比较选择相对最优的选择。
索引的匹配规则
简单说明索引是如何创建的:当以多个值建立联合索引时,会按从左到右的顺序进行排序,也就是假如建立的索引为(A, B, C)
,
那么会先按列A
进行排序,直到A
值完全一样,再依照B
进行排序,C
同理。
索引这种规则也就解释了最左前缀原则,当希望使用索引时,应该包含索引从左开始的连续字段,因为查询的顺序也就是按建立索引的顺序来的。
提供几个样例来说明:
- 字符串的模糊查询是可以使用索引的(如果建立了的话),因为以
name
排序的过程中,实际是字符串的比较排序,而字符串的比较是从左到右一个个字符比较大小的来,这和建立联合索引的规则类似,比较好理解。
SELECT * FROM person_info WHERE name LIKE 'As%';
- 条件查询中
and
连接的条件顺序不影响索引的使用,SQL语句执行前有查询优化器。
SELECT * FROM person_info WHERE B = '1' and C = '2' and A = '0';
范围值的匹配
如下的例子是可以使用索引的。
SELECT * FROM person_info WHERE name > 'Asa' AND name < 'Barlow';
按name
排序之后,对范围内的查询相当于分别查找两个边界值,再去节点之间的链表值。
不要忘记了B+
树的叶子节点是链表构成的,有了边界节点,在链表中遍历就能找到期望数据了。
注意!
范围查询可以使用索引是有要求的,简单总结成一句话就是,范围查询的左边都是精确查询,而范围查询的右边无法使用索引。解释一下:
联合索引是从左向右依次排序的,也就是只有在当前比较列之前的列完全相同的情况下,索引才是有效的。而左边全是精确查询就确保了 在范围查询前之前的列值是完全一样的,就可以通过索引查找范围,而找到一个范围值之后剩下查询的值就不能通过索引保证排序的(当前在一个链表中假设按A
进行排序,而之后的B
,C
在单独的一个A
值下才是有序的,在这个列表中则表现为无序)。
关于关键字
-
Order By
之后的顺序遵循最左前缀原则,同时如果要使用索引,顺序必须一致,order by
就是一个按提供键 顺序排序的过程。同时注意不要
ASC
,DESC
混合使用。这样也会导致索引效率低下。 -
WHERE
中最好只有建立了索引的列,如果出现了未建立索引的列,那么查询只能先将符合条件的记录提取出来之后再进行排序。同样的,
WHERE
条件中对索引列不要使用表达式和函数,这会导致查询将先对表达式进行计算,构建成新的列再排序。 Group by
这点和Order By
是完全类似的。
关于回表
回表同样也是建立使用索引必须要权衡的代价之一。考虑这样一个查询
SELECT * FROM person_info WHERE name > 'Asa' AND name < 'Barlow';
在范围查找之后,要选择出全部的数据,那么就必然需要拿着索引中找到的主键去回表。
- 访问二级索引使用
顺序I/O
,访问聚簇索引使用随机I/O
。
需要回表的记录越多,使用二级索引的性能就越差。甚至可能因为回表过多,MySQL
会将索引优化成全表扫描
。所以使用索引时尽可能指定需要的数据,而不要使用来表示,同时尽可能选择索引中包含的列值*,这样就不会进行回表。
覆盖索引是一种解决回表的方式,但代价是存储了相当的冗余数据。
挑选索引
只列出几条大致的规则:
只为用于搜索,排序,和分组的列创建索引
-
考虑列的基数
列的基数
指某一列中不重复数据的个数。如果一个列中重复的数据过多,基数过小(例如性别数据只有男,女,基数为2
)那么建立索引的效果就特别差,因为在节点中查找时几乎没有区分数据的功能 -
索引列的类型尽量小
这是考虑到建立,维护索引的代价。以整数类型为例,有
TINYINT
、MEDIUMINT
、INT
、BIGINT
这么几种,它们占用的存储空间依次递增,我们这里所说的类型大小
指的就是该类型表示的数据范围的大小。能表示的整数范围当然也是依次递增,如果我们想要对某个整数列建立索引的话,在表示的整数范围允许的情况下,尽量让索引列使用较小的类型,比如我们能使用INT
就不要使用BIGINT
,能使用MEDIUMINT
就不要使用INT
~数据类型越小,查询时比较操作越快
数据类型越小,索引所占空间更小,一个页面中可以存放更多的索引数据,变相减少了磁盘的
I/O
次数
-
使用字符串值的前缀
这点是针对字符串可能过长的情况,理由和上面类似,为了减少查询和维护时的代价,仅以字符串前几位进行比较建立索引。之后的值可以在一个小范围内遍历查找,这样牺牲了极小的性能省出了更多的空间和查找时间。
主键的选取
关于主键的选择,一般MySQL
会使用标记为Unique
的列构建索引,如果没有则会创建一个隐藏的自增列用于排序(在行格式,列结构中提到过)。对于大多数情况来说,主键最好选择一个非业务的列,因为自增的值是便于在页
中插入行数据
的,如果不是顺序的键很有可能数据插入过程中被分配在不同的页
,而插入新值后又需要不断调整行数据
的位置导致页分裂
之类的问题,影响效率。