索引使用的注意事项(策略及优化)
并不是建立索引就能显著提高查询速度,在索引的使用过程中,存在一些使用细节和注意事项,因为稍不留心,就可能导致在查询过程中索引失效。
一下列举一些需要注意的事项:
1. 不要在列上使用函数
不要在列上使用函数,这将导致索引失效而进行全表扫描。
如:
1
|
select * from news where year(publish_time) < 2018 |
应改为:
1
|
select * from news where publish_time < '2018-01-01'
|
2. 不要在列上进行计算
不要在列上进行运算,这也将导致索引失效而进行全表扫描。
如:
1
|
select * from news where id / 100 = 1
|
应改为:
1
|
select * from news where id = 1 * 100
|
3. 尽量避免使用 != 或 not in或 <> 等否定操作符
应该尽量避免在 where 子句中使用 != 或 not in 或 <>操作符,
这些负向查询也会导致索引失效而进行全表扫描。
如:
1
|
select name from user where id not in (1,3,4);
|
应改为:
1
|
select name from user where id in (2,5,6);
|
4. 尽量避免使用 or 来连接条件
应该尽量避免在 where 子句中使用 or来连接条件,因为这会导致索引失效而进行全表扫描。
如:
1
|
select * from CRM_CUSTOMER_INFO where id= 1 or id =2
|
应改为
1
|
select * from CRM_CUSTOMER_INFO where id in(1,2)
|
5. 字段的默认值不要为 null
只要列中包含有 NULL 值都将不会被包含在索引中,复合索引中只要有一列含有 NULL值,那么这一列对于此复合索引就是无效的。
因此,在数据库设计时,除非有一个很特别的原因使用 NULL 值,不然尽量不要让字段的默认值为 NULL。
6. 不要让数据库帮我们做隐式类型转换
当查询条件左右两侧类型不匹配的时候会发生隐式转换,隐式转换带来的影响就是可能导致索引失效而进行全表扫描。
1
|
select name from user where telno=1888888888
|
这样虽然可以查出数据,但是会导致全表扫描。
应改为
1
|
select name from user where telno='1888888888'
|
7. 前导模糊查询会导致索引失效
like 的方式进行查询,在 like “value%” 可以使用索引,但是对于 like “%value%” 这样的方式,执行全表查询,这在数据量小的表,不存在性能问题,但是对于海量数据,全表扫描是非常可怕的事情。所以,根据业务需求,考虑使用 ElasticSearch 或 Solr 是个不错的方案。
8. 数据区分不明显的不建议创建索引
如 user 表中的性别字段,可以明显区分的才建议创建索引,如身份证等字段
9. 可以用复合索引替代多个单列索引
MySQL 只能使用一个索引,会从多个索引中选择一个限制最为严格的索引,因此,为多个列创建单列索引,并不能提高 MySQL 的查询性能。
假设,有两个单列索引,分别为 news_year_idx(news_year) 和 news_month_idx(news_month)。现在,有一个场景需要针对资讯的年份和月份进行查询,那么,SQL 语句可以写成:
select * from news where news_year = 2017 and news_month = 1
事实上,MySQL 只能使用一个单列索引。为了提高性能,可以使用复合索引 news_year_month_idx(news_year, news_month) 保证 news_year 和 news_month 两个列都被索引覆盖。
10. 覆盖索引的好处
如果一个索引包含所有需要的查询的字段的值,直接根据索引的查询结果返回数据,而无需读表,能够极大的提高性能。因此,可以定义一个让索引包含的额外的列,即使这个列对于索引而言是无用的。
11. 范围查询对多列查询的影响
查询中的某个列有范围查询,则其右边所有列都无法使用索引优化查找。
举个例子,假设有一个场景需要查询本周发布的资讯文章,其中的条件是必须是启用状态,且发布时间在这周内。那么,SQL 语句可以写成:
select * from news where publish_time >= ‘2017-01-02’ and publish_time <= ‘2017-01-08’ and enable = 1
这种情况下,因为范围查询对多列查询的影响,将导致 news_publish_idx(publish_time, enable) 索引中 publish_time 右边所有列都无法使用索引优化查找。换句话说,news_publish_idx(publish_time, enable) 索引等价于 news_publish_idx(publish_time) 。
对于这种情况,我的建议:对于范围查询,务必要注意它带来的副作用,并且尽量少用范围查询,可以通过曲线救国的方式满足业务场景。
例如,上面案例的需求是查询本周发布的资讯文章,因此可以创建一个news_weekth 字段用来存储资讯文章的周信息,使得范围查询变成普通的查询,SQL 可以改写成:
1
|
select * from news where news_weekth = 1 and enable = 1
|
然而,并不是所有的范围查询都可以进行改造,对于必须使用范围查询但无法改造的情况,我的建议:不必试图用 SQL 来解决所有问题,可以使用其他数据存储技术控制时间轴,例如 Redis 的 SortedSet 有序集合保存时间,或者通过缓存方式缓存查询结果从而提高性能。
12. 复合索引(联合索引)
首先介绍一下联合索引。联合索引其实很简单,相对于一般索引只有一个字段,联合索引可以为多个字段创建一个索引。它的原理也很简单,比如,我们在(a,b,c)字段上创建一个联合索引,则索引记录会首先按照A字段排序,然后再按照B字段排序然后再是C字段,因此,联合索引的特点就是:
第一个字段一定是有序的
当第一个字段值相等的时候,第二个字段又是有序的,比如下表中当A=2时所有B的值是有序排列的,依次类推,当同一个B值得所有C字段是有序排列的
A | B | C |
---|---|---|
1 | 2 | 3 |
1 | 4 | 2 |
1 | 1 | 4 |
2 | 3 | 5 |
2 | 4 | 4 |
2 | 4 | 6 |
2 | 5 | 5 |
其实联合索引的查找就跟查字典是一样的,先根据第一个字母查,然后再根据第二个字母查,或者只根据第一个字母查,但是不能跳过第一个字母从第二个字母开始查。这就是所谓的最左前缀原理。
13. 复合索引的最左前缀原理
在复合索引的基础上,再来详细介绍一下联合索引的查询。还是复合索引中的例子,我们在(a,b,c)字段上建了一个联合索引,所以这个索引是先按a 再按b 再按c进行排列的,所以:
以下的查询方式都可以用到索引
select * from table where a=1;
|
上面三个查询按照 (a ), (a,b ),(a,b,c )的顺序都可以利用到索引,这就是最左前缀匹配。
如果查询语句是:
select * from table where a=1 and c=3; 那么只会用到索引a。
|
如果查询语句是:
select * from table where b=2 and c=3; 因为没有用到最左前缀a,所以这个查询是用户到索引的。
|
如果用到了最左前缀,但是顺序颠倒会用到索引吗?
比如:
select * from table where b=2 and a=1;
|
如果用到了最左前缀而只是颠倒了顺序,也是可以用到索引的,因为mysql查询优化器会判断纠正这条sql语句该以什么样的顺序执行效率最高,最后才生成真正的执行计划。但我们还是最好按照索引顺序来查询,这样查询优化器就不用重新编译了。