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_insert1\G
#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;