zoukankan      html  css  js  c++  java
  • 12--正确使用索引,索引下推技术,explain使用

    一、索引测试

    1.准备测试数据

    #1. 准备表
    create table t1(
    id int,
    name varchar(20),
    gender char(6),
    email varchar(50)
    );
    
    #2. 创建存储过程,实现批量插入记录
    delimiter $$ #声明存储过程的结束符号为$$
    create procedure auto_insert1()
    BEGIN
        declare i int default 1;
        while(i<3000000)do #300w数据
        insert into t1 values(i,'mm','male',concat('mm',i,'@boy'));
        set i=i+1;
        end while;
    END$$  #$$结束
    delimiter ; #重新声明分号为结束符号
    
    #3. 查看存储过程
    show create procedure auto_insert1G 
    
    #4. 调用存储过程
    call auto_insert1(); # 过程会很慢,一分钟后可强制结束,也已将创建了一些
    

    2.未创建索引前的查询速度缓慢

    #无索引:从头到尾扫描一遍,所以查询速度很慢
    mysql> select * from s1 where id=333;
    +------+---------+--------+----------------+
    | id   | name    | gender | email          |
    +------+---------+--------+----------------+
    |  333 | mm| male     | cm333@oldboy   |
    +------+---------+--------+----------------+
    1 row in set (1.33 sec)
    
    mysql> explain select * from s1 where id=333;
    
    mysql> select * from s1 where email='cm333@oldboy';
    +------+---------+--------+----------------+
    | id   | name    | gender | email          |
    +------+---------+--------+----------------+
    |  333 | cm333   | male   | cm333@oldboy   |
    +------+---------+--------+----------------+
    1 row in set (1.50 sec)
    

    3.加上索引后查询速度极快

    1. 一定是为搜索条件的字段创建索引,比如select * from s1 where id > 5;就需要为id加上索引
    
    2. 在表中已经有大量数据的情况下,建索引会很慢,且占用硬盘空间,插入删除更新都很慢,只有查询快
    比如create index idx on s1(id);会扫描表中所有的数据,然后以id为数据项,创建索引结构,存放于硬盘的表中。
    建完以后,再查询就会很快了
    
    mysql> create index idx on s1(id);
    
    3. 需要注意的是:innodb表的索引会存放于s1.ibd文件中,而myisam表的索引则会有单独的索引文件table1.MYI
    
    mysql> select * from s1 where id=333;
    +------+---------+--------+----------------+
    | id   | name    | gender | email          |
    +------+---------+--------+----------------+
    |  333 | cm333   | male   | cm333@oldboy   |
    +------+---------+--------+----------------+
    1 row in set (0.01 sec)
    
    #  建立完索引,那个数据的ibd文件是变大了,但是如果删除完索引,但是那个ibd文件却不会变小,这是一个bug
    #  因为ibd里面的数据和索引里面的内容都放在ibd文件里了
    

    二、正确使用索引

    1、索引命中也未必会加速

    并不是说我们创建了索引就一定会加快查询速度,若想利用索引达到预想的提高查询速度的效果,我们在添加索引时,必须遵循以下问题
    
    # 1、如何正确使用索引
    1、以什么字段的值为基础构建索引
    2、最好是不为空、唯一、占用空间小的字段
    
    # 2、针对全表查询语句如何优化?
    应用场景:用户想要浏览所有的商品信息
    select count(id) from s1;
    
    如何优化:
    开发层面分页查找,用户每次看现从数据中拿
    
    # 3、针对等值查询
    以重复度低字段为基础创建索引加速效果明显
    create index xxx on s1(id);
    select count(id) from s1 where id = 33;
    
    以重复度高字段为基础创建索引加速效果明显
    create index yyy on s1(name);
    select count(id) from s1 where name="egon"; -- 速度极慢
    select count(id) from s1 where name!="egon";  -- 速度快
    select count(id) from s1 where name="yy";  -- 速度快
    
    以占用空间大字段为基础创建索引加速效果不明显
    
    # 总结:给重复低、且占用空间小的字段值为基础构建索引
    
    # 4、关于范围查询
    select count(id) from s1 where id=33;
    select count(id) from s1 where id>33;
    select count(id) from s1 where id>33 and id < 36;
    select count(id) from s1 where id>33 and id < 1000000;
    
    # 总结:innodb存储能加速范围查询,但是查询范围不能特别大
    
    5、关于条件字段参与运算
    select count(id) from s1 where id*12 = 10000;
    select count(id) from s1 where id = 10000/12;
    select count(id) from s1 where func(id) = 10000/12;
    
    # 总结:不要让条件字段参与运算,或者说传递给函数
    

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

    2、大于号、小于号

    3、不等于!=

    4、 between …and…

    5、like

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

    6、索引下推技术

    索引下推技术默认是开启的

    # 删除索引xxx
    mysql> drop index xxx on t1;
    Query OK, 0 rows affected (0.01 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> desc t1;
    +--------+-------------+------+-----+---------+-------+
    | Field  | Type        | Null | Key | Default | Extra |
    +--------+-------------+------+-----+---------+-------+
    | id     | int(11)     | YES  |     | NULL    |       |
    | name   | varchar(20) | YES  |     | NULL    |       |
    | gender | char(6)     | YES  |     | NULL    |       |
    | email  | varchar(50) | YES  | MUL | NULL    |       |
    +--------+-------------+------+-----+---------+-------+
    4 rows in set (0.00 sec)
    
    # and查询
    # 对于连续多个and: mysql会按照联合索引,从左到右的顺序找一个区分度高的索引字段(这样便可以快速锁定很小的范围),加速查询。
    # 区分度最高的是email(命中普通索引),所以制作联合索引为(email,name,gender)
    mysql> explain select count(id) from t1 where name="nana" and email="nana666@haha" and gender="male";
    +----+-------------+-------+------+---------------+------+---------+-------+------+------------------------------------+
    | id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra                              |
    +----+-------------+-------+------+---------------+------+---------+-------+------+------------------------------------+
    |  1 | SIMPLE      | t1    | ref  | zzz           | zzz  | 203     | const |    1 | Using index condition; Using where |
    +----+-------------+-------+------+---------------+------+---------+-------+------+------------------------------------+
    
    # or查询
    # 对于连续多个or: mysql会按照条件的顺序,从左到右依次判断,即联合索引为(name,email,gender),并不会加速查询
    mysql> explain select count(id) from t1 where name="nana" or email="nana666@haha" or gender="male";
    

    and与or的逻辑:

    条件1 and 条件2:所有条件都成立才算成立,但凡要有一个条件不成立则最终结果不成立
    
    条件1 or 条件2:只要有一个条件成立则最终结果就成立
    

    and连接的多个条件属于小范围,or连接的多个条件属于大范围。

    对于连续多个and:

    mysql会按照联合索引,从左到右的顺序找一个区分度高的索引字段(这样可以快速锁定很小的范围),加速查询。

    对于连续多个or:

    mysql会按照条件的顺序,从左到右依次判断,并不会加速查询。

    7、其他情况:不走索引

    # 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; 
    
    

    8、总结索引使用原则

    - 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、连表时注意条件类型需一致
    

    9、联合索引与最左前缀匹配原则

    条件中需要用到多个字段,并且多次查询中的多个字段都包含某一个字段,即可创建联合索引

    什么时候创建联合索引?

    条件中需要用到多个字段,并且多次查询中的多个字段都包含某一个字段。
    创建联合索引需要注意的问题:

    重复度低且占用空间较小的字段应该尽量往左放,让其称为最左前缀。
    使用联合索引,满足加速条件的规则:

    使用select查询数据的时候,where条件中必须包含最左前缀的字段,命中索引才可以实现加速

    三、慢查询优化的基本步骤

    # 0.先运行看看是否真的很慢,注意设置SQL_NO_CACHE
    # 1.where条件单表查,锁定最小返回记录表。这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始查起,单表每个字段分别查询,看哪个字段的区分度最高
    # 2.explain查看执行计划,是否与1预期一致(从锁定记录较少的表开始查询)
    # 3.order by limit 形式的sql语句让排序的表优先查
    # 4.了解业务方使用场景
    # 5.加索引时参照建索引的几大原则
    # 6.观察结果,不符合预期继续从0分析
    

    四、explain使用

    之前总说到查询速度、查询效率,那使用什么去查看查询的快慢呢,就用explain

    1.explain用法

    我们之前查询过SQL、

    #1.查询中国和美国的城市 or 和 in
    mysql> select * from world.city where countrycode='CHN' or countrycode='USA';
    mysql> select * from world.city where countrycode in ('CHN','USA');
    
    #2.union all (联合查询) 讲索引的时候再说
    mysql> select * from world.city where countrycode=‘USA’ union all select * from world.city where countrycode=‘CHN’;
    
    #3.explain用法
    explain select * from city where countrycode='USA' or countrycode='CHN';
    explain select * from city where countrycode in ('USA','CHN');
    explain select * from world.city where countrycode='USA' union all select * from world.city where countrycode='CHN';
    

    2.explain结果注解

    mysql> explain select id from student where id=2;
    
    #ID:
    id相同,执行顺序由上至下
    id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
    #type:
    查询使用了哪种类型
    #possible_keys:
    可能应用在这张表中的索引,一个或多个
    #key:
    实际使用的索引,如果为NULL,则没有使用索引
    #key_len (可以使用前缀索引控制)
    表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度,在不损失精确性的情况下,长度越短越好。
    #ref
    级别是否在ref之上
    #rows
    根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数,也就是说,用的越少越好
    #Extra
    Using filesort:使用order by时
    Using temporary:使用排序order by和分组查询group by时
    

    3.查询数据种类

    1)全表扫描
    
    #1.什么是全表扫描
    在explain语句结果中type为ALL,MySQL将遍历全表以找到匹配的行
    
    #2.什么时候出现全表扫描?
    1.业务确实要获取所有数据
    mysql> explain select * from city;
    
    2.不走索引导致的全表扫描
    2.1 没索引
    mysql> explain select * from city where District=‘shanghai’;
    #创建索引后速度会提升,变成索引扫描了
    mysql> alter table city add index idx_District(District);
    mysql> explain select * from city where District=‘shanghai’;
    2.2 索引创建有问题
    2.3 语句有问题
    
    2)索引扫描
    
    #常见的索引扫描类型:
    1)index 全索引扫描,index与ALL区别为index类型只遍历索引树(性能也不高)
    mysql> explain select population from city;
    
    2)range 范围查询,只检索给定范围的行,使用一个索引来选择行,一般来说,SQL语句只要达到range级别,就可以了
    mysql> explain select * from city where countrycode in (‘USA’,‘CHN’);
    
    3)ref 精确查找,匹配条件有普通索引
    #先给population列一个索引
    mysql> alter table city add index idx_population(population);
    #然后查询
    mysql> explain select * from city where population=300000000;
    
    4)eq_ref 类似ref,区别就在使用的索引是唯一索引
    explain select city.name,city.countrycode,country.name from city join country on city.countrycode=country.code where city.population<100;
    
    5)const 使用的索引是主键索引
    mysql> explain select * from country where code=‘CHN’;
    
    6)system system是const类型的特例,当查询的表只有一行的情况下
    mysql> delete from country where code != ‘CHN’;
    #有外键删除不了
    
    7)null 执行过程不用访问表或索引,直接选取索引列的最小值
    mysql> explain select min(population) from city;
    
  • 相关阅读:
    洛谷—— P3353 在你窗外闪耀的星星
    洛谷—— P1238 走迷宫
    洛谷—— P1262 间谍网络
    9.8——模拟赛
    洛谷—— P1189 SEARCH
    算法
    May 22nd 2017 Week 21st Monday
    May 21st 2017 Week 21st Sunday
    May 20th 2017 Week 20th Saturday
    May 19th 2017 Week 20th Friday
  • 原文地址:https://www.cnblogs.com/caodan01/p/15018534.html
Copyright © 2011-2022 走看看