正确使用索引

一 索引命中也未必会加速

并不是说我们创建了索引就一定会加快查询速度,若想利用索引达到预想的提高查询速度的效果,我们在添加索引时,必须遵循以下问题

1 范围问题,或者说条件不明确,条件中出现这些符号或关键字:>、>=、<、<=、!= 、between…and…、like、

大于号、小于号

不等于!=

between …and…

like:%或者_应该放在右边,并且左边的字符应该尽可能地精确一些,这样锁定的范围才会小一些,例如like “egon3%”而不是like “%egon3″、like “eg%”

2 尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录

#先把表中的索引都删除,让我们专心研究区分度的问题

#先把表中的索引都删除,让我们专心研究区分度的问题 mysql> desc s1; +——–+————-+——+—–+———+——-+ | Field | Type | Null | Key | Default | Extra | +——–+————-+——+—–+———+——-+ | id | int(11) | YES | MUL | NULL | | | name | varchar(20) | YES | | NULL | | | gender | char(5) | YES | | NULL | | | email | varchar(50) | YES | MUL | NULL | | +——–+————-+——+—–+———+——-+ 4 rows in set (0.00 sec) mysql> drop index a on s1; Query OK, 0 rows affected (0.20 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> drop index d on s1; Query OK, 0 rows affected (0.18 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> desc s1; +——–+————-+——+—–+———+——-+ | Field | Type | Null | Key | Default | Extra | +——–+————-+——+—–+———+——-+ | id | int(11) | YES | | NULL | | | name | varchar(20) | YES | | NULL | | | gender | char(5) | YES | | NULL | | | email | varchar(50) | YES | | NULL | | +——–+————-+——+—–+———+——-+ 4 rows in set (0.00 sec)


分析原因

我们编写存储过程为表s1批量添加记录,name字段的值均为egon,也就是说name这个字段的区分度很低(gender字段也是一样的,我们稍后再搭理它)

回忆b+树的结构,查询的速度与树的高度成反比,要想将树的高低控制的很低,需要保证:在某一层内数据项均是按照从左到右,从小到大的顺序依次排开,即左1<左2<左3<...

而对于区分度低的字段,无法找到大小关系,因为值都是相等的,毫无疑问,还想要用b+树存放这些等值的数据,只能增加树的高度,字段的区分度越低,则树的高度越高。极端的情况,索引字段的值都一样,那么b+树几乎成了一根棍。本例中就是这种极端的情况,name字段所有的值均为'egon'

#现在我们得出一个结论:为区分度低的字段建立索引,索引树的高度会很高,然而这具体会带来什么影响呢???

#1:如果条件是name='xxxx',那么肯定是可以第一时间判断出'xxxx'是不在索引树中的(因为树中所有的值均为'egon’),所以查询速度很快

#2:如果条件正好是name='egon',查询时,我们永远无法从树的某个位置得到一个明确的范围,只能往下找,往下找,往下找。。。这与全表扫描的IO次数没有多大区别,所以速度很慢

3 索引列不能参与计算,保持列“干净”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’)

4 索引下推技术的应用

 

索引下推技术:指的是在联合索引的基础上可以继续往后比较后续的字段来进一步筛选,以此减少回表操作,5.6才有默认就开启https://zhuanlan.zhihu.com/p/408570713
联合索引就是把n个字段组合到一起当成key值,联合索引肯定属于辅助索引,与单个字段制作的辅助索引相同的是,他们的叶子节点放的都是索引值及其对应的主建,不同的的是联合索引的叶子节点放的索引值是由多个字段拼到一起的,索引下推就利用这拼起来的多个字段,挨个比较,直到筛选出一个最终的结果,最后那那些这些结果对应的主建id去聚集索引里进行回表查询

 

and/or

#1、连续的多个and条件
条件1 and 条件2 and 条件3,所有条件都成立才算成立,锁定的范围小
所以对于连续多个and条件,先判断哪一个并没有差别,反正最后都需要成立才行,
因此mysql的优化器对于连续多个and条件,会制定出所有可能的查询计划:
计划1:以条件1的字段为基础筛选出一批记录,然后在验证这批记录是否符合条件2与条件3
计划2:以条件2的字段为基础筛选出一批记录,然后在验证这批记录是否符合条件1与条件3
计划3:以条件3的字段为基础筛选出一批记录,然后在验证这批记录是否符合条件1与条件2

最终mysql优化器会选出一个最佳的计划,即以某个条件的字段值为基础筛选出的记录最小,这就是索引下推技术,egon老师给举个例子你一下子就明白了
例如:相亲,你需要从一千人中选出符合自己条件的相亲对象,这一千人就好比一张表中的一千条数据,一系列and条件就是你的择偶标准(择偶标准肯定不能是一系列or条件,你说egon说的对不对)
    你的择偶条件是:性别=女 and 年龄 = 18 and 长相 = 好看
    问,如何筛选会以最快的速度找出适配对象,很明显,不应该按照从左到右的顺序检索,因为“性别=女”的一大堆啊,
    既然多个and条件一定是需要同时成立的才可以,那么无论先比哪个都可以啊,反正最终都得满足条件才可以,所以我们
    完整可以先以一个比较苛刻(即锁定范围较小的条件)条件锁定一小部分人,然后再从这一些部分人群中判断是否符合其他条件
    比如先拿着"长相=好看"这个条件来筛选,那么一千人可能只剩下了10个人了,然后在判断这十个人是否满足“性别=女 and 年龄=18“这样效率就高了
    这就是索引下推技术的原理,即连续的多个and条件并非是按照从左到右的顺序计算的,但这种技术也仅适用于连续的多个and条件
    
#2、连续的多个or条件
条件1 or 条件2 or 条件3,只要有一个条件成立则最终结果就成立,锁定的范围大

对于连续的多个or条件,mysql只能按照条件的顺序,从左到右依次判断了,egon老师还是给你举个方便理解的例子
例如:选出汽车的残次品,一定是一系列or条件,因为但凡一个零件坏了就算是残次品
    你的筛选条件是:方向盘坏了 or 轮毂坏了 or 刹车坏了
    这个时候你只能按照从左到右的顺序依次判断条件来进行筛选了,根本不能像连续多个and那样先用某个条件锁定一个小范围,例如你以轮毂坏了为条件
    筛出一些残次品,并不能在此基础上判断其他提交,因为但凡是一个条件满足,都算是残次品

在左边条件成立但是索引字段的区分度低的情况下(name与gender均属于这种情况),会依次往右找到一个区分度高的索引字段,加速查询

经过分析,在条件为name=’egon’ and gender=’male’ and id>333 and email=’xxx’的情况下,我们完全没必要为前三个条件的字段加索引,因为只能用上email字段的索引,前三个字段的索引反而会降低我们的查询效率

5 最左前缀匹配原则(详见第八小节),非常重要的原则,对于组合索引mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配(指的是范围大了,有索引速度也慢)

例如
条件:a = 1 and b = 2 and c > 3 and d = 4 
应该创建的联合索引是
(a,b,d,c),即把锁定范围大的字段往右放
依据索引下推技术a,b,d的顺序是可以任意调整的

6、其他情况:不走索引

# 1)没有查询条件,或者查询条件没有用到索引列
没有查询条件
mysql> explain select * from world.city;

没有用索引列做条件
mysql> explain select * from world.city where 1=1;
mysql> explain select * from world.city where name='shanghai';

# 2)查询结果集是原表中的大部分数据,应该是25%以上
如果生产中,必须有这种全表扫描的需求不走索引
mysql> explain select * from world.city where population > 50;

如果业务允许,可以使用limit控制,可以走索引
mysql> explain select * from world.city where population>50 limit 10;

如果业务不允许,可以使用缓存
前面加上缓存,memcached,redis

# 3)索引本身失效,统计数据不真实
反复修改,插入数据,索引被修改坏了,每次都会进行排序

# 4)查询条件使用函数在索引列上或者对索引列进行运算,运算包括(+,-,*等)
mysql> explain select * from world.city where id=9;
mysql> select * from tb1 where reverse(email) = 'egon';

查询ID等于9的数据,会导致不走索引
mysql> explain select * from world.city where id-1=8;

查询ID等于9的数据,可以在后面作加减
mysql> explain select * from world.city where id=8+1;

# 5)隐式转换,会导致索引失效
如果列是字符串类型,传入条件是必须用引号引起来,不然...
select * from tb1 where email = 999;

创建一个表
create table test(id int, name varchar(20), phonenum varchar(10));

插入几条数据
insert into test values(1,'jc','110'),(2,'xf',119),(3,'jh',120);

建立索引,没有相同值可以给唯一索引
alter table test unique key idx_num(phonenum);

查看索引
show index from test;

查询语句级别
不走索引
explain select * from test where phonenum=110;
走索引
explain select * from test where phonenum='110'; -- 引号可能导致大事故
因为这一列是varchar类型,必须以字符来查询


# 6)<> 和 not in  , or 也不走索引
mysql> explain select * from test where phonenum <> '120';
mysql> explain select * from test where phonenum not in (120);
mysql> explain select * from test where telnum='110' or telnum='119';

使用union all可以走索引
mysql> explain select * from test where telnum='110' union all select * from test where telnum='119';

# 7)like模糊查询%在最前面,不走索引
%在前面不走索引
mysql> explain select * from city where countrycode like '%HN';

%在后面,走索引
mysql> explain select * from city where countrycode like 'CH%';

放在后面也不是一定了,因为涉及到第二点结果的占总数据的比例

%在最前面的搜索需求,建议使用elasticsearch  ES  ELK(E) 搜索引擎式的 数据库

# 8)单独引用联合索引里非第一位置的索引列
组合索引最左前缀
如果组合索引为:(name,email)
name and email -- 命中索引
name -- 命中索引
email -- 未命中索引

# 9)排序条件为索引,则select字段必须也是索引字段,否则无法命中
- order by
select name from s1 order by email desc;
当根据索引排序时候,select查询的字段如果不是索引,则速度仍然很慢
select email from s1 order by email desc;
特别的:如果对主键排序,则还是速度很快:
select * from tb1 order by nid desc;

二 总结索引使用原则

- 1、在创建索引的时候,会把该列所有的数据按照btree的方式进行排序

- 2、为常作为查询条件的字段建立索引

- 3、限制索引的数目,不要每列都创建索引
每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。
修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。

- 4、在同一列上,尽量避免创建多个索引,可以创建多个但是它们是有优先级的,先走一个就不会再走另一个索引了;
    alter table student add index idx_name(name);
    alter table country add unique key uni_name(name);

- 5、避免对大列建索引,在数据很长的列上创建前缀索引

- 6、如果可以创建唯一索引,就创建唯一索引(该列的数据不重复),查询速度快

- 7、不要对重复度高的字段创建索引

- 8、索引不要参与计算

- 9、为经常要排序,分组,联合操作的列,创建联合索引
经常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段,排序操作会浪费很多时间。
如果为其建立索引,可以有效地避免排序操作

- 10、尽量使用前缀来索引
创建索引的时候,可以给该列所有数据进行排序
create index xxxx on tb(title(19)) # text类型,必须制定长度

- 11、删除不再使用或者很少使用的索引
表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。

- 12、避免使用select *

- 13、count(1)或count(列) 代替 count(*),ps:mysql中没有差别了

- 14、创建表时尽量时 varchar 代替 char

- 15、表的字段顺序固定长度的字段优先

- 16、使用连接(JOIN)来代替子查询(Sub-Queries)

- 17、连表时注意条件类型需一致
上一篇
下一篇
Copyright © 2022 Egon的技术星球 egonlin.com 版权所有 沪ICP备2022009235号 沪公网安备31011802005110号 青浦区尚茂路798弄 联系方式-13697081366