MySQL索引失效的14种场景分享
作者:美丽的程序人生
作为一名后端程序员,经常会对慢查询SQL语句进行调优,而SQL语句出现慢查询,很多情况是由于索引失效造成的。结合实际demo场景和平时的开发经验,给大家分享数据库索引失效场景。 如下图所示,梳理了14种常见索引失效的场景:
1、字段类型隐式转换
新建一张测试表student结构如下:
CREATE TABLE `student`( `id` int (11) NOT NULL AUTO_INCREMENT, `stu_no` varchar(12) NOT NULL, `age` int(11) NOT NULL, `name` varchar(255) NOT NULL, PRIMARY KEY(`id`), KEY `idx_stu_no`(`stu_no`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8;
① 执行如下一条sql,type为ALL,并未走索引:
② 将sql字段条件带上单引号,执行结果,根据type和key,发现索引生效:
结论:为什么第一条sql没有走索引呢?因为where条件中stu_no为varchar类型,在与数字类型进行匹配时,MySQL会做隐式类型转换,导致索引失效;因此,编写sql时要保证索引字段与匹配数据类型一致。
2、查询条件中包含or
新建一张测试表student结构如下:
CREATE TABLE `student`( `id` int (11) NOT NULL AUTO_INCREMENT, `stu_no` varchar(12) NOT NULL, `age` int(11) NOT NULL, `name` varchar(255) NOT NULL, PRIMARY KEY(`id`), KEY `idx_stu_no`(`stu_no`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8;
执行如下一条包含or关键字的sql,发现并未走索引
**结论:**为什么以上sql没走索引呢,因为Mysql存在优化器,当单独按照stu_no查询时走索引,单独按照age查询时全表扫描,就需要索引+全表+合并三步,优化器考虑性能和成本,直接全表扫描也是合理的。
拓展: a. or走索引与否,与优化器的预估有关,即使连接条件都设置了索引,也可能因为回表导致索引失效; b. 索引优化器的存在,就是找到一个索引扫描行数最少的方案去执行语句,扫描行数根据统计信息来预估的值,这个统计信息就是我们常说的索引的“区分度”; c. 一个索引上不同的值越多,索引的区分度就越好。我们把一个索引上不同的值的个数,称之为 "索引基数"。也就是说,基数越大,索引的区分度就越好,执行查询的行数就越少。因此索引最好创建在不同值较多的字段上。
3、like 通配符% 错误使用
新建一张测试表student结构如下:
CREATE TABLE `student`( `id` int (11) NOT NULL AUTO_INCREMENT, `stu_no` varchar(12) NOT NULL, `age` int(11) NOT NULL, `name` varchar(255) NOT NULL, PRIMARY KEY(`id`), KEY `idx_stu_no`(`stu_no`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8;
① 执行如下一条sql,like '%值',%在左边,发现并未走索引;
② 执行如下一条sql,like '%值%',,发现并未走索引;
③ 执行如下一条sql,like '值%',%在右边,发现索引生效;
④ 执行如下sql,like '%值%,但是select只查询stu_no列,发现索引生效';
结论: like查询以%开头,会导致索引失效。可以有两种方式优化: a. 使用覆盖索引优化,只查询索引列; b. 把%放后面,索引生效
拓展: 索引包含所有满足查询需要的数据的索引,称为覆盖索引(Covering Index)。
4、联合索引最左匹配原则
新建测试表:(有一个联合索引 idx_stu_no_age,stu_no在前, age在后)
CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `stu_no` varchar(12) NOT NULL, `age` int(11) NOT NULL, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`), KEY `idx_stu_no_age` (`stu_no`,`age`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
① 使用stu_no作为条件,发现索引生效;
② 使用stu_no、age作为条件,发现索引仍然生效;
③ 使用age作为条件,发现不走索引;
④ 使用age作为条件,但是只查询select stu_no ,age 发现索引生效。
结论: a. 当我们创建一个联合索引的时候,如(a,b,c),相当于创建了(a)、(a,b)和(a,b,c)三个索引,这就是最左匹配原则; b. 联合索引不满足最左原则,索引一般会失效,第四种情况命中索引是因为查询列覆盖索引。
5、索引列使用MySQL函数,索引失效
新建一张测试表student结构如下:
CREATE TABLE `student`( `id` int (11) NOT NULL AUTO_INCREMENT, `stu_no` varchar(12) NOT NULL, `age` int(11) NOT NULL, `name` varchar(255) NOT NULL, PRIMARY KEY(`id`), KEY `idx_stu_no`(`stu_no`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8;
执行如下sql语句,由于查询条件字段中使用MySQL内置函数,导致索引失效
6、索引列存在计算,使用(+、-、*、/),索引失效
新建一张测试表student结构如下:
CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `stu_no` varchar(12) NOT NULL, `age` int(11) NOT NULL, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`), KEY `idx_student_age` (`age`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
虽然student表age已经创建索引,但是查询条件中对索引列进行计算导致索引失效,如下图所示:
7、使用(!= 或者 < >,not in),导致索引失效
新建一张测试表student结构如下:
CREATE TABLE `student`( `id` int (11) NOT NULL AUTO_INCREMENT, `stu_no` varchar(12) NOT NULL, `age` int(11) NOT NULL, `name` varchar(255) NOT NULL, PRIMARY KEY(`id`), KEY `idx_stu_no`(`stu_no`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8;
虽然student表stu_no字段添加索引,但是使用!= 或者<>,not in 导致索引失效,如下图所示:
建议: a. 对于不等于的优化,如果数据量较大可以考虑反向操作优化; b. 对于not in 优化,可以采用left join 和 右表.id is null 方法优化。
8、使用is null, is not null,导致索引失效
新建一张测试表student结构如下:
CREATE TABLE `student`( `id` int (11) NOT NULL AUTO_INCREMENT, `stu_no` varchar(12) NOT NULL, `age` int(11) NOT NULL, `name` varchar(255) NOT NULL, PRIMARY KEY(`id`), KEY `idx_stu_no`(`stu_no`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8;
执行如下sql,发现索引失效,如下图所示:
建议: 对于is null 和is not null导致索引失效的问题,需要从数据库设计层面进行优化,尽量避免表里面存在空值或者为空时,表字段可以设置默认值。
9、左连接、右连接关联字段编码不一致,索引失效
新建两张测试表,一张student表、一张class_sources表
CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `stu_no` varchar(12) NOT NULL, `age` int(11) NOT NULL, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`), KEY `idx_stu_no` (`stu_no`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `class_sources` ( `id` int(11) NOT NULL AUTO_INCREMENT, `stu_no` varchar(12) CHARACTER SET utf8mb4 NOT NULL, `age` int(11) NOT NULL, `class_name` varchar(255) NOT NULL, PRIMARY KEY (`id`), KEY `idx_stu_no` (`stu_no`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
student表的stu_no字段编码是utf8,而class_sources表的stu_no字段编码为utf8mb4。 执行左外连接查询,class_sources表还是走全表扫描,如下:
如果把两边表对应字段的编码改为一致,同样的sql还是会走索引,如下图:
结论: 在sql中做表关联时,需要注意两边字段的编码要保持一致。
10、使用了select *,导致索引失效
虽然在规范手册中没有提到索引方面的问题,但禁止使用select * 语句可能会带来的附带好处就是:某些情况下可以走覆盖索引,查询字段均为索引时; select * 容易带来如下问题:
1)增加查询分析器解析成本。
2)增减字段容易与 resultMap 配置不一致。
3)无用字段增加网络 消耗,尤其是 text 类型的字段。
11、两字段列做比较,导致索引失效
新建测试表student
CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `stu_no` varchar(12) NOT NULL, `age` int(11) NOT NULL, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`), KEY `idx_stu_no` (`stu_no`) USING BTREE, KEY `idx_age` (`age`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
如下图所示sql,对字段stu与age字段进行比较,索引失效:
结论: 在sql中避免使用字段进行比较。
12、order by使用,导致索引失效
新建测试表
CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `stu_no` varchar(12) NOT NULL, `age` int(11) NOT NULL, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`), KEY `idx_age` (`age`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
如下图所示sql,使用order by排序导致索引失效:
结论: 由于使用order by 需要对全表数据进行排序,因此会索引失效,但是有个特例,如果order by 后面跟的是主键,也会走索引,有时候也与mysql的优化器有关。
13、参数不同,导致索引失效
① 当Mysql发现通过索引扫描的行记录数超过全表的10%-30%时,优化器可能会放弃走索引,自动变成全表扫描。某些场景下即便强制SQL语句走索引,也同样会失效。 ② 类似的问题,在进行范围查询(比如>、< 、>=、<=、in等条件)时往往会出现上述情况,而上面提到的临界值根据场景不同也会有所不同。
14、group by 使用违反最左匹配原则,导致索引失效
新建测试表student
CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `stu_no` varchar(12) NOT NULL, `age` int(11) NOT NULL, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`), KEY `student_stu_no_IDX` (`stu_no`,`age`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
① 满足最左匹配原则,索引生效,如下图所示:
② 当违背最左匹配原则时,索引失效,如下图所示:
到此这篇关于MySQL索引失效的14种场景分享的文章就介绍到这了,更多相关MySQL索引失效内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!