MySQL的高性能索引策略
前言
首先,如果不是很理解索引的底层结构原理的话,建议先看一下我的另一篇介绍索引的底层原理。(点击跳转)
本篇文章是大部分摘入高性能MySQL的笔记,也有参考网上的各种博客整理。仅供大家学习和复习!
独立的列
独立的列是指索引不能参与运算,要将索引索引单独的放在表达式的一列。像下面的语句就不会用到索引。
mysql> SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;
我们可以很清楚的看出来,actor_id就是4,所以没必须要这样表达,而放弃了使用索引。或者将索引拿去参与其他的运算也是不行的,只有保证独立的列才可以走索引。
前缀索引和索引选择性
索引选择性
虽然建立索引有助于我们对数据库查询的优化,但是建立索引也不是必须的。像下面这些情况就不适合建立索引。
- ①表记录比较少(小于2000),不必建索引,直接全表扫描速度更快
- ②索引的选择性比较低,即字段中存在大量重复的值
所以我们对查询次数高且数据量比较大的表建立索引。而且还要选择重复率低的字段当作索引,索引的选择性越高则查询效率越高,因为选择性高的索引可以让MySQL在查找的时候过滤掉更多的行。像唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
前缀索引
这里要提到,我们的索引是会占用的额外的存储空间的。所以如果索引特别长的话,那么维护索引将会占用非常大的空间。但其实一般情况下,某个列前缀的选择性也是足够高的。对于BLOB、TEXT或者很长的VARCHAR类型的列,就必须使用前缀索引,因为MySQL不允许这些列的完整长度。
重点在于选择足够长的前缀以保证比较高的选择性,同时又不能太长(以便节约空间)。
具体的策略方法有如下:
- 可以先统计出最常见的出现的值,然后再不断去尝试取不同长度的前缀索引,看前缀索引取多长的时候,能和最常见的出现的值大致匹配。
- 首先计算全表的选择性,然后取不同长度的前缀,看选择性什么时候接近全表的选择性
多列索引
为每一个列都建立一个索引并不是最好的,但是mysql在5.0之后更新版本引入了索引合并的策略,可以将多个单独的索引合并使用查询。关于多列索引的索引合并策略虽然是一种优化的结果,但也说明了我们的表上的索引建得很糟糕。下面是一些注意的点:
(1)在mysql5.0之前如果两个单个索引之间使用or的话,会进行全表查询,所以建议使用union的方式。
(2)当出现服务器对多个索引做相交操作时候(通常有多个AND条件),通常意味着需要一个包含所有相关列的多列索引,而不是一个个的单独索引。
(3)当服务器对多个索引做联合操作的时候(通常有多个OR条件),通常需要耗费大量CPU和内存资源在算法的缓存、排序和合并操作上。特别是在我们的索引选择性不高,需要合并扫描返回大量数据的时候。
选择合适的索引列顺序
前面说到了多列索引,但是在多列索引里面还有一个值得我们去考虑的问题就是如何选择一个合适的索引列的顺序。但是在选择顺序主要是遵守这么一条法则:越是在前面的索引,越是能过滤掉大量的数据。
(1)在不考虑排序和分组的时候,将选择性最高的列放在前面通常是一个比较好的选择。可以优化与WHERE的查询,对于只使用索引部分前缀列的查询来说选择性也更高。
(2)在数据量小并且选择性也很高的放在前面,也更容易过滤掉大量的数据。
(3)如果可以,尽可能的使用唯一索引放在前面。因为唯一索引的选择性是1,可以极大的提升查询效率。
聚簇索引
聚簇索引并不是一个单独的索引类型,而是一种数据存储方式。在InnoDB中,聚簇索引实际上在同一个结构中保存了B-Tree索引和数据行。节点页只包含索引列,叶子也才包含行的全部数据。由于聚簇索引数据行和相邻的键值紧凑地存储在一起,也就是说,无法把数据行和索引分开存储,因此,由于数据行只有一份,聚簇索引在一个表只有一个。
InnoDB通过主键聚集索引;如果没有定义主键,则会选择一个唯一的非空索引替代;如果还是没有这样的索引,则会隐式定义一个主键来作为聚簇索引。InnoDB只聚集同一个页面中的记录,包含相邻键值的页面可能会相距甚远。
之所以需要聚簇索引,是因为聚集的数据也有优点:
- 可以把相关数据保存在一起。例如实现电子邮箱时,可以根据用户ID来聚集数据,这样只需要从磁盘读取少数的数据页就可以获取整个用户的全部邮件。如果没有使用聚簇索引,则每一封邮件都可能导致一次磁盘IO。
- 数据访问更快。聚簇索引将索引和数据保存在同一个B-Tree中,因此从聚簇索引中获取数据通常比在非聚簇索引中查找更快。
- 使用覆盖索引扫描的查询可以直接使用页节点中的主键值。
覆盖索引
什么 是覆盖索引呢?以InnoDB为例,我们在二级索引查询的时候,需要先查找到主键,然后再去回表查询。但是如果我们的索引的叶子结点下面包含所有需要查询的字段的值,我们就称为覆盖索引。
只需要从覆盖索引不需要回表查询我们就知道它有多少好处了:
- 索引一般都会比数据行小,如果只需要读取索引的话,那么MySQL就会极大地减少数据访问量,同时也因为更容易放入内存中对I/O密集型的应用也有帮助。
- 因为索引是按照顺序存储的,所以对于I/O密集型的范围查询会比随机从磁盘读取每一行数据的I/O操作少得多。
- InnoDB的二级索引在叶子节点保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询。
有时候,当查询语句无法使用到覆盖索引,可以使用延迟关联的方式,来进行查询。
延迟关联:可以先利用覆盖索引,将符合条件的主键全都查询出来,再统一利用主键去join主表。
使用索引扫描来做排序
只有当索引的顺序和ORDER BY
子句的顺序完全一致,并且所有列的排序方向都一样时,MySQL才能够使用索引来对结果做排序。如果查询需要关联多张表,则只有当ORDER BY
子句引用的字段全部为第一个表时,才能使用索引做排序。ORDER BY
子句和查找型查找的限制是一样的:需要满足索引的最左前缀的要求,否则无法利用索引排序。
压缩(前缀压缩)索引
MyISAM使用前缀压缩来减少索引的大小,从而让更多的索引可以放入内存中,提高系能。默认只压缩字符串,但通过参数设置也可以对整数做压缩。
压缩过程
保存索引块中的第一个值,然后将其他值和第一个值进行比较得到相同前缀的字节数和剩余的不同后缀部分。例如,索引块中的第一个值是“perform”,第二个值是“performance”,那么第二个值的前缀压缩后存储的是类似“7,ance”这样的形式。
可以在CREATE TABLE语句中指定PACK_KEYS参数来控制索引压缩的方式。
冗余与重复索引
重复索引
MySQL允许在相同列上创建多个索引,无论是有意的还是无意的。但是针对这些重复的索引,却是需要单独的去维护,并且优化器在优化查询的时候也需要逐个进行考虑,影响性能。
具体重复建立索引的例子如下:
在创建了主键之后又对主键进行添加唯一和索引。但是事实上,MySQL的唯一限制和主键限制都是通过索引实现的。这种写法实际上就是创建了三个重复的索引!
冗余索引
冗余索引和重复索引有一些不同。比如如果创建了索引(A,B),再创建索引(A)就是冗余索引,因为这只是前一个索引的前缀索引。
所以我们在需要新索引的时候,尽量地去扩展它,而不是去新建它。比如表中已有索引(A),现在要添加(A,B)索引的时候只需要去修改原来的索引即可,这样子就可以避免原来的索引成为了冗余索引。
索引和锁
使用InnoDB,只有在访问行时才会对其加锁,而索引能够减少InnoDB访问的行数,从而减少行的数量。但是这种情况下,如果索引无法过滤掉无效的行,会导致间隙锁
,并且只有在事务提交后才能释放锁。如下面的查询语句:
这条查询仅仅会返回2~4之间的行,但是实际上获取了1-4之间的排它锁,会锁住第一行,因为是通过actor_id<5
来获取,没有排除第一行,所以会锁住所有actor_id
小于5的行。
最左前缀优化
- 情况1——全列匹配: 当按照索引中的所有列进行精确匹配(
=
或in
)时,索引可以被用到。其次,由于MySQL的优化,这种情况下,where
中条件的顺序其实可以可以颠倒,MySQL会对这种情况下的条件进行重排,使其和索引顺序一致。 - 情况2——最左前缀匹配: 当索引由多个字段组成,但查找时,只用到多个字段中最左边的前几个字段。这种情况下,会进行最左前缀匹配的索引。
- 情况3——查询条件用到了索引中列的精确匹配,但是中间某个条件未提供: 比如索引是
(col1,col2,col3)
,查询的where条件是col1='value' and col2='value2'
,这样只能用到col1
的索引,不会用到col3
的索引。 - 情况4——查询条件没有指定索引第一列: 由于不是最左前缀,用不到索引。
- 情况5——范围查询: 在where中使用
>
、<
等范围条件,范围列能用到索引, 但是范围列后面的列无法用到索引。 - 情况7——查询条件中含有函数或表达式: 使用函数或表达式,不会建立索引。
- 情况8-模糊查询尽量使用后缀匹配:也就是%尽量放在关键字后面。
索引失效的情况
总结就是:
- 遵守最左匹配原则,中间断索引,使用范围查询
- 在索引列上做计算
- 索引字段使用 != 或者 <>
- 索引字段使用 is null 或者 is not null
- 使用通配符 %开头
- 索引字段是字符串,查询条件没有使用字符串
- 索引字段使用or
参考资料
高性能MySQL第三版