上一章我们讲述了索引调优实战在Join的过程,那么本章重点阐述索引失效的场景及原因剖析!
1、索引失效场景
老规矩先导入一些表作为数据使用,表的所有定义在这个链接中:
Mysql高级调优篇表补充——建表SQL_风清扬逍遥子的博客-CSDN博客⭐️tbl_emp⭐️CREATE TABLE `tbl_emp` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(20) DEFAULT NULL,`deptId` int(11) DEFAULT NULL,PRIMARY KEY (`id`) ,KEY `fk_dept_id`(`deptId`))ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8;⭐️tbl_dhttps://blog.csdn.net/qq_31821733/article/details/120886389?spm=1001.2014.3001.5501 导入staffs员工表:
1.1、全值匹配我最爱
我们添加了索引:
看第一个Sql:select * from staffs where name='July';
我建立了复合索引分别是name,age,pos,我只查一个,索引会不会失效?
看下执行计划可以知道走索引上,你建立三个,我走了1个,没问题
再来一个Sql:select * from staffs where name='July' and age = 25;
执行计划知道,走部分索引一样没问题。
但是不知道有没有发现一个问题!!Extra为Null??这个是第一次出现,那么这里我要重点强调下,Extra为Null的时候,如果走了索引,说明这个查询,进行了回表!!
那么什么是回表呢?简单来说,如果你查询的字段,存在非索引字段,那么查询的时候,Mysql虽然根据了你的条件得到了这个记录,但是不在索引的字段无法通过索引的方式直接得到,只能通过拿到该条记录的主键索引,再从数据行里读,我们知道Mysql索引文件和数据文件是在两个不同的文件里的,要去读磁盘;所以索引文件建立的效果,就是帮助我们对数据进行排序和查找效率的优化,不至于去读数据行进行额外的IO开销;
所以这里字段我用select *,因为复合索引里没有add_time这个字段,所以无法直接查出来add_time这个列的记录,要通过定位到主键,然后再读一次数据行才可以得到这个记录,称为回表。
如果我这么写,就不会出现回表,因为pos在索引列中!
那我再加一个字段pos,很明显精度越高,key_len的长度就越长。总结是【全值匹配我最爱】
现在三种2情况都正常,我们来看一些特殊场景!
explain select * from staffs where age = 23 and pos = 'dev'; 执行计划走一下发现
索引怎么不走了?再看看这个sql:select * from staffs where pos = 'dev';
索引也不走!
再来一个sql:select * from staffs where name = 'zhangsan';走索引了
总结下来:如果查询中没有开头的索引,也就是不告诉我1楼在哪里,让我从2楼或者3口开始找索引,不好意思,我找不到,只能全表扫!违背了【最佳左前缀法则】
回顾下我们建立的索引,(name, age, pos)顺序是1楼,2楼,3楼;如果索引了多列,要遵守最左前缀法则,指的是查询从索引的最左前列开始并且不跳过索引中的列。即【带头大哥不能死】
再看下这个sql:select * from staffs where name = 'zhangsan' and pos = 'dev'; 这个sql等于中间2楼断开了,执行计划显示这个key_len和只有name的时候一样,说明只走了name索引,Extra中出现Using index condition,这个是5.6后新加的特性,会先条件过滤索引,过滤完索引后找到所有符合索引条件的数据行,随后用 WHERE 子句中的其他条件去过滤这些数据行;其实没有什么用,就是走到了索引上的意思。【中间兄弟不能断】
总结:全值匹配我最爱,最左前缀要遵守;带头大哥不能死,中间兄弟不能断;
1.2、勿在索引列做任何操作
不要在索引列上做任何操作,包括计算,函数,自动或者手动类型转换,会导致索引失效而转向全表扫描。
举例子:explain select * from staffs where left(name, 4) = 'July'; 查找name左往右4个字符为July的行。索引失效了!
总结:【索引列上少计算】
1.3、范围之后全失效
这个上一节提过,like后面的索引会失效,即Mysql存储引擎不能使用索引中,范围条件右边的列!
举例子,explain select * from staffs where name = 'July' and age > 14 and pos = 'manager'; 因为2楼已经是范围查找了,age用到了索引,进行范围查找,但是后面的索引pos就失效了,因为你告诉我2楼,没告诉我去3楼的路,所以我找不到3楼的目录,定位不到3楼,只能挨个去找入口!这里要注意,5.7以前的优化,是如果出现了范围查找,则当前范围的索引也不走,而5.7后,范围索引之后的才失效,所以这里的key_len=78,单个name话是74,三个都走是140。
1.4、尽量使用覆盖索引
查询语句中只访问索引的查询,索引列和查询列尽量保持一致,或者查询列<=索引列:
看这个sql:explain select * from staffs where name = 'July' and age = 15 and pos = 'manager'; 如果用了* ,我说过会回表!但是指定了索引的列,那么就走了覆盖索引,Using index是很好的效果!
包括以下的场景也是一样!
1.5、不等于场景下索引失效
Mysql在使用不等于的场景下,无法使用索引导致全表扫描
explain select * from staffs where name != 'July'; 执行计划后发现
以及explain select * from staffs where name <> 'July';
1.6、is null、is not null无法使用索引
看下这个sql:explain select * from staffs where name is null; 这个就很离谱了
explain select * from staffs where name is not null;
1.7、Like百分写最右
like以通配符开头('%abc...')时,Mysql索引会失效变成全表扫!
比如这个sql:explain select * from staffs where name like '%July%';
如果是这样的,也是一样:explain select * from staffs where name like '%July'; 原因我这里不多解释了,上几章节都有。
但是这样的话,就不会失效:explain select * from staffs where name like 'July%';
因为like是范围查找,百分号在后面,Mysql会拿到字典序进行排序的方式查找对应的情况,而百分号在前面,Mysql就不知道从哪个字母开始找,于是便全表扫描。
那这个是不是解决不了?实际面试中经常会这么问:如何解决like '%xxx%' 字符时索引不被使用的情况?
答案是用覆盖索引避免索引失效,我们这里的索引是(name, age, pos),索引我们在查询的时候不要写select *,只要写具体的字段值,任何一个列被覆盖索引覆盖,就可以解决两边百分号的问题!!!
如果我这么写的话,肯定不行,因为add_time不在覆盖索引内
总结:【like百分写最右】+【覆盖索引解决双百分问题】
1.8、字符串不加单引号索引失效
比如这个sql:explain select * from staffs where name = 222; 索引失效
而这个是成功走到索引的:explain select * from staffs where name = '222';
Mysql很聪明,你以为你给我的我就查不到了,你给我的Int型的时候,实际这个字段是varchar型,传入数字会隐式的帮你转换成varchar类型,前面说过不要让Mysql做这些自动或者手动的类型转换,否则索引失效!当然查询的结果,是不会有变化的,只是sql执行上有转换。
1.9、少用or
少用or,会导致索引失效,不是不用;
比如explain select * from staffs where name = 'July' or name = 'z3'; 虽然都是带头大哥的name,但是索引一样丢失了。
2、总结口诀
- 全值匹配我最爱,最左前缀要遵守;
- 带头大哥不能死,中间兄弟不能断;
- 索引列上少计算,范围之后全失效;
- Like百分写最右,覆盖索引不写星;
- 不等空值还有or,索引失效要少用;
- VAR引号不可丢,SQL高级并不难!
这些口诀,可以帮助我们的记忆!
本章讲的这一切,才是真真正正能写出高性能的JavaEE系统的实战核心关键知识点,以后写sql或多或少都会在脑子里记忆,都会自我分析,为上线前减少了很多不必要的开销,也是晋级高级或者架构师的必备之路,共勉!下一章节,索引面试总结和调优其他开发细节知识点!