- 1 索引的使用常识(如何编写SQL语句避免索引失效)
- 1-1 情况1:全值匹配 ,对复合索引中所有列都指定具体值
- 1-2 情况2:复合索引遵循最左前缀法则
- 1-3 情况3:复合索引中如果有字段进行范围查询,那么该字段后面的字段的索引就会失效。
- 1-4 情况4:在索引字段上进行运算操作, 将造成该字段索引将失效
- 1-5 情况5:字符串不加单引号,造成该字段索引失效。
- 1-6 情况6:尽量使用覆盖索引,避免select * (尽量只查询建立索引的字段,使得索引表中包含这些字段,避免回表查询)
- 1-7 情况7:”where 索引字段的条件限制 or 非索引字段的条件限制“情况下,索引字段的索引会失效,不被使用。
- 1-8 情况8:使用模糊查询%的位置不当也会造成索引失效
- 1-9 情况9:某些情况数据库优化器会认为全表扫描比使用索引快。
- 1-10 情况10:where 条件匹配使用is NULL和is NOT NULL,有时索引会失效。
- 1-11 情况11:where中使用in进行匹配走索引, 使用not in不会使用索引
- 1-12 多个单列索引和一个复合索引优先建立复合索引
- 2 索引的使用情况查询
- 参考资料
1 索引的使用常识(如何编写SQL语句避免索引失效)
创建测试表
--创建表格,主键为sellerid
create table `tb_seller` (
`sellerid` varchar (100),
`name` varchar (100),
`nickname` varchar (50),
`password` varchar (60),
`status` varchar (1),
`address` varchar (100),
`createtime` datetime,
primary key(`sellerid`)
)engine=innodb default charset=utf8mb4;
--插入多条数据
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('alibaba','阿里巴巴','阿里小
店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('baidu','百度科技有限公司','百度小
店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('huawei','华为科技有限公司','华为小
店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('itcast','传智播客教育科技有限公司','传智播
客','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('itheima','黑马程序员','黑马程序
员','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('luoji','罗技科技有限公司','罗技小
店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('oppo','OPPO科技有限公司','OPPO官方旗舰
店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('ourpalm','掌趣科技股份有限公司','掌趣小
店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('qiandu','千度科技','千度小
店','e10adc3949ba59abbe56e057f20f883e','2','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('sina','新浪科技有限公司','新浪官方旗舰
店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('xiaomi','小米科技','小米官方旗舰
店','e10adc3949ba59abbe56e057f20f883e','1','西安市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('yijia','宜家家居','宜家家居旗舰
店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
--创建了复合索引,为name,status,addresss三个字段创建索引
create index idx_seller_name_sta_addr on tb_seller(name,status,address);
1-1 情况1:全值匹配 ,对复合索引中所有列都指定具体值
explain select * from tb_seller where name='小米科技' and status='1' and address='北京
市'G;
- 从explain的语句查询的信息可以看到查找类型的方式是ref,表示查找记录的过程中会使用到索引。
1-2 情况2:复合索引遵循最左前缀法则
基本要求:查询利用索引的时候从索引定义的最左列字段开始匹配,只有成功匹配才会匹配下一个
create index idx_seller_name_sta_addr on tb_seller(name,status,address);
注意:最左前缀并不是要求where后面的子句的顺序与索引定义一样,而是在匹配索引的时候,按照索引定义的时候字段顺序先查看有没有name字段,然后再查看有没有status字段,依次类推,只有有一个没有检查到,那么后面的字段就都不会检查了。
--命令0,下面的命令效果都是等同的。
explain select * from tb_seller where status='1' and address='北京市' and name='小米科技'G;
explain select * from tb_seller where status='1' and name='小米科技' and address='北京市'G;
--命令1:违反最左前缀法则,没有以最左边的字段开头,索引失效
explain select * from tb_seller where status='1' and address='北京市'G;
--命令2:符合最左原则,但中间跳了一列,那只有最左边索引失效
explain select * from tb_seller where name='小米科技'and address='北京市'G;
上图是命令1的执行结果,违反最左前缀法则,导致查找类型为all,即扫描整个数据表,索引失效。
图中key_len为403,说明只用了name作为索引。
1-3 情况3:复合索引中如果有字段进行范围查询,那么该字段后面的字段的索引就会失效。
注意点:如果某个字段使用了范围查询,那么该字段后面的字段匹配要尤为注意。
explain select * from tb_seller where status>'1' and address='北京市' and name='小米科技'G;
上图中从key_len可以看到status使用范围查询,造成后匹配addr字段索引失效。
1-4 情况4:在索引字段上进行运算操作, 将造成该字段索引将失效
explain select * from tb_seller where substring(name,3,2)='科技'G;
1-5 情况5:字符串不加单引号,造成该字段索引失效。
失效原因: 没有对字符串加单引号,MySQL的查询优化器,会自动的进行类型转换,造成索引失效(类似于情况4在索引上进行运算)。
explain select * from tb_seller where status=0 and name='小米科技'G;
可以看到status是字符串,但是由于使用数字作为匹配条件,造成字段失效。
1-6 情况6:尽量使用覆盖索引,避免select * (尽量只查询建立索引的字段,使得索引表中包含这些字段,避免回表查询)
覆盖索引的概念:
- 尽量使用覆盖索引(只访问索引的查询(索引列完全包含查询列)),减少select * 。
更通俗的解释:select的数据列从索引中就能够取得,不必从数据表中读取,换句话说查询列要被所使用的索引覆盖(在索引表中包含有查询的字段,因此可以从索引表中获得,不需要到原表中取数据)
上图中using index condition表示当前select查询的信息需要回到原表中去获取,原因是因为需要访问数据表中所有的字段,而在索引表中只包含建立索引的字段,因此需要回表查询数据。
上图中查询的字段只有name,在索引表中就可以获取,因此不需要回表查询。从extra信息也可以看出。
explain之extra字段信息 | 说明 |
---|---|
using index | 使用覆盖索引的时候就会出现 |
using where | 在查找使用索引的情况下,需要回表去查询所需的数据 |
using index condition | 查找使用了索引,但是需要回表查询数据 |
using index ; using where | 查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据 |
1-7 情况7:”where 索引字段的条件限制 or 非索引字段的条件限制“情况下,索引字段的索引会失效,不被使用。
explain select * from tb_seller where name='黑马程序员' or createtime = '2088-01-01
12:00:00'G;
上图中可以看到name字段是建立了索引,但是createtime字段是没有索引的,通过or语句连接,实际执行并没有去使用name字段建立的索引。
1-8 情况8:使用模糊查询%的位置不当也会造成索引失效
--命令1:模糊符号在前会造成索引失效
explain select * from tb_seller where name like '%科技'G;
--命令2:模糊查询符号在后不会造成索引失效
explain select * from tb_seller where name like '科技%'G;
--命令3:索引失效
explain select * from tb_seller where name like '%科技%'G;
高效的模糊匹配查询:使用覆盖索引
基本思想:使用select语句时,要求查询的字段都已经建立索引。
--采用下面的写法,那么模糊匹配时就会走覆盖索引
explain select name from tb_seller where name like '%科技%'G;
explain select name,status from tb_seller where name like '%科技%'G;
explain select name,status,address from tb_seller where name like '%科技%'G;
从上图中可以看到,当仅仅查询建立索引的字段时,即便是采用了模糊查询,也能够在查询时以覆盖索引的方式加快查询的速度。
1-9 情况9:某些情况数据库优化器会认为全表扫描比使用索引快。
--对address字段单独建立索引,然后使用该字段匹配查询
create index address_index on tb_seller(address);
explain select * from tb_seller where address="北京市"; --不走索引
explain select * from tb_seller where address="西安市"; --走索引
从上图中可以看到:
- 匹配的字段是“西安市”,使用了索引
- 匹配的字段是“北京市”,则没有使用索引
原因在于该字段12条记录中,11个值是北京市,因此数据库服务层的优化器认为比起扫描索引字段,直接去扫描全表更加快,因此此次查询到索引就失效了。
1-10 情况10:where 条件匹配使用is NULL和is NOT NULL,有时索引会失效。
原因:与情况9非常类似,数据库的优化器会进行判定进行全表扫描还是使用索引。
实例:数据库中某个字段的值绝大部分都是null的话且该字段建立索引,实际使用 select * from table where 字段 is NULL, 那么查询时会进行全表扫描而不是索引。
1-11 情况11:where中使用in进行匹配走索引, 使用not in不会使用索引
注意: 实际查询时尽量避免使用not in。
1-12 多个单列索引和一个复合索引优先建立复合索引
尽量使用复合索引,而少使用单列索引
当创建复合索引时:一个复合索引实际索引的个数等于所用的字段数,查询时每次可以使用复数个索引进行查询。
create index idx_name_sta_address on tb_seller(name, status, address);
就相当于创建了三个索引 :
name
name + status
name + status + address
创建多个单列索引时:
create index idx_seller_name on tb_seller(name);
create index idx_seller_status on tb_seller(status);
create index idx_seller_address on tb_seller(address);
数据库会选择一个最优的索引(辨识度最高索引)来使用,并不会使用全部索引 。
2 索引的使用情况查询
show status like 'Handler_read%';
show global status like 'Handler_read%';
名称 | 索引信息 |
---|---|
Handler_read_first | 索引中第一条被读的次数。如果较高,表示服务器正执行大量全索引扫描(这个值越低越好。 |
Handler_read_key | 如果索引正在工作,这个值代表一个行被索引值读的次数,如果值越低,表示索引得到的性能改善不高,因为索引不经常使用(这个值越高越好 |
Handler_read_next | 按照键顺序读下一行的请求数。如果你用范围约束或如果执行索引扫描来查询索引列该值增加。 |
Handler_read_prev | 按照键顺序读前一行的请求数。该读方法主要用于优化ORDER BY ... DESC |
Handler_read_rnd | 根据固定位置读一行的请求数。如果你正执行大量查询并需要对结果进行排序该值较高。你可能使用了大量需要MySQL扫描整个表的查询或你的连接没有正确使用键。这个值较高,意味着运行效率低,应该建立索引来补救。 |
Handler_read_rnd_next | 在数据文件中读下一行的请求数。如果你正进行大量的表扫描,该值较高。通常说明你的表索引不正确或写入的查询没有利用索引 |
上面表格信息了解即可。
参考资料
20210305