MySQL系列:索引失效场景总结
相关文章
数据库系列:MySQL慢查询分析和性能优化
数据库系列:MySQL索引优化总结(综合版)
数据库系列:高并发下的数据字段变更
数据库系列:覆盖索引和规避回表
数据库系列:数据库高可用及无损扩容
数据库系列:使用高区分度索引列提升性能
数据库系列:前缀索引和索引长度的取舍
数据库系列:MySQL引擎MyISAM和InnoDB的比较
数据库系列:InnoDB下实现高并发控制
数据库系列:事务的4种隔离级别
数据库系列:RR和RC下,快照读的区别
数据库系列:MySQL InnoDB锁机制介绍
数据库系列:MySQL不同操作分别用什么锁?
数据库系列:业内主流MySQL数据中间件梳理
数据库系列:大厂使用数据库中间件解决什么问题?
1 介绍
在笔者的这篇文章《构建高性能索引(策略篇)》中,我们详细讨论了如何设计高质量索引,里面多个地方提及可能导致索引失效的场景。
这边咱们重新梳理下,以枚举的方式来梳理出所有可能出现索引失效的点,避免RD同学们踩坑。
2 数据准备及验证
2.1 数据准备
1、创建两个表:员工表和部门表
/*部门表,存在则进行删除 */
drop table if EXISTS dep;
create table dep(
id int unsigned primary key auto_increment,
depno mediumint unsigned not null default 0,
depname varchar(20) not null default "",
memo varchar(200) not null default ""
);
/*员工表,存在则进行删除*/
drop table if EXISTS emp;
create table emp(
id int unsigned primary key auto_increment,
empno mediumint unsigned not null default 0,
empname varchar(20) not null default "",
job varchar(9) not null default "",
mgr mediumint unsigned not null default 0,
hiredate datetime not null,
sal decimal(7,2) not null,
comn decimal(7,2) not null,
depno mediumint unsigned not null default 0
);
2、创建两个函数:生成随机字符串和随机编号
/* 产生随机字符串的函数*/
DELIMITER $
drop FUNCTION if EXISTS rand_string;
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmlopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str = CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i+1;
END WHILE;
RETURN return_str;
END $
DELIMITER;
/*产生随机部门编号的函数*/
DELIMITER $
drop FUNCTION if EXISTS rand_num;
CREATE FUNCTION rand_num() RETURNS INT(5)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(RAND()*100+RAND()*20);
RETURN i;
END $
DELIMITER;
3、编写存储过程,模拟500W的员工数据
/*建立存储过程:往emp表中插入数据*/
DELIMITER $
drop PROCEDURE if EXISTS insert_emp;
CREATE PROCEDURE insert_emp(IN START INT(10),IN max_num INT(10))
BEGIN
DECLARE i INT DEFAULT 0;
/*set autocommit =0 把autocommit设置成0,把默认提交关闭*/
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO emp(empno,empname,job,mgr,hiredate,sal,comn,depno) VALUES ((START+i),rand_string(6),'SALEMAN',0001,now(),2000,400,rand_num());
UNTIL i = max_num
END REPEAT;
COMMIT;
END $
DELIMITER;
/*插入500W条数据*/
call insert_emp(0,5000000);
4、编写存储过程,模拟120条部门数据
/*建立存储过程:往dep表中插入数据*/
DELIMITER $
drop PROCEDURE if EXISTS insert_dept;
CREATE PROCEDURE insert_dept(IN START INT(10),IN max_num INT(10))
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i+1;
INSERT INTO dep( depno,depname,memo) VALUES((START+i),rand_string(10),rand_string(8));
UNTIL i = max_num
END REPEAT;
COMMIT;
END $
DELIMITER;
/*插入120条数据*/
call insert_dept(0,120);
5、建立关键字段的索引,这边是跑完数据之后再建索引,会导致建索引耗时长,但是跑数据就会快一些。
# 这边建立一个复合索引,包含 depno(部门编号)、empname(员工姓名)、job(工作岗位)。
create index idx_depno_empname_job on emp(depno,empname,job);
2.2 验证过程
在 MySQL 中建设合理高效的索引是提升检索性能的最有效方式,因为索引可以快速地定位表中的某条记录,达到提高数据库查询的速度的目的。
大多数情况下都(默认)采用B+树来构建索引,我们下面也默认使用InnoDB引擎来举例。
2.2.1 违反最左匹配原则
如下图,b+树的数据项是复合的数据结构,比如(empname,depno,job)这种(即构建一个联合索引)时,b+树是按照从左到右的顺序来建立搜索树的。
示例:
1、当以('brand',106,'SALEMAN')这样的数据来检索的时候,b+树会优先比较empname来确定下一步的所搜方向,如果empname相同再依次比较depno和job,最后得到检索的数据。
2、但如果是(106,'SALEMAN')这样,没有empname的数据来的时候,b+树就不知道下一步该查哪个节点,因为empname就是第一个比较因子,必须要先根据empname来搜索才能知道下一步去哪里查询。
3、再比如当('brand','SALEMAN')这样的数据来检索时,b+树可以用empname来指定搜索方向,但下一个字段depno的缺失,所以只能把名字等于 'brand' 的数据都扫描出来,然后再匹配职位是SALEMAN的数据了。
这个重要特征就是索引的最左匹配原则,按照这个原则执行索引效率特别高。
我们试试在b+树上分析和举例:
下图中是3个字段(depno,empname,job)的联合索引,数据以depno asc,empname asc,job asc这种排序方式存储在节点中的,
排序原则:
1、索引以depno字段升序
2、depno相同时,以empname字段升序,
3、empname相同的时候,以job字段升序

第一位置
检索depno=7的记录
由于页中的记录是以depno asc,empname asc,job asc这种排序方式存储的,所以depno字段是有序的,可以通过二分法快速检索到,步骤如下:
1、将Disk1加载到内存中
2、在内存中对Disk1中的记录采用二分法找,可以确定depno=7的记录位于{7,Brand,1}和{7,dyny,1}关联的范围内,这两个所指向的页分别是 Disk2 和 Disk4。
3、加载页Disk2,在Disk2中采用二分法快速找到第一条depno=7的记录,然后通过链表向下一条及下一页开始扫描,直到在Disk4中找到第一个不满足depno=7的记录为止。

第一+第二位置
检索depno=7 and empname like 'B%'的记录
步骤跟上面是一致的,可以确定depno=1 and empname like 'B%'的记录位于{7,Band,1}和{7,Bec,1}关联的范围内,查找过程和depno=7查找步骤类似。

第二位置
检索empname like 'C%'的记录
这种情况通过Disk1页中的记录,无法判断empname like 'C%' 的记录在哪些页中的,只能逐个加载索引树的页,对所有记录进行遍历,然后进行过滤,此时索引无效。
# 验证脚本:未使用到了索引,全表扫描
mysql> explain select empno,empname,job from emp where empname like 'C%';
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | emp | NULL | ALL | NULL | NULL | NULL | NULL | 4982087 | 11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
第三位置
检索job=8的记录
这种情况和查询 empname like 'C%' 也一样,也只能扫描所有叶子节点,索引也无效。
# 验证脚本:未使用到了索引,全表扫描
mysql> explain select empno,empname,job from emp where job=8;
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | emp | NULL | ALL | NULL | NULL | NULL | NULL | 4982087 | 10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 2 warnings (0.00 sec)
第二+第三位置
empname和job一起查
这种原理跟前面两个一致,无法使用索引,只能对所有数据进行扫描。
第一+第三位置
按照(depno,job)字段顺序检索
这种仅使用到索引中的depno字段了,通过depnon确定范围之后,加载所有depno下的数据,再对job条件进行过滤。如果的depno查出来的数据基数巨大,也会慢。
比如我们的测试数据中 depno=16 的数据有50W左右,也是比较多的。
# 验证脚本:未使用到了索引,但仅覆盖了depno,所以扫描行数也有 37626 行
mysql> explain select empno,empname,job from emp where depno=7 and job=8;
+----+-------------+-------+------------+------+-----------------------+-----------------------+---------+-------+-------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+-----------------------+-----------------------+---------+-------+-------+----------+-----------------------+
| 1 | SIMPLE | emp | NULL | ref | idx_depno_empname_job | idx_depno_empname_job | 3 | const | 37626 | 10.00 | Using index condition |
+----+-------------+-------+------------+------+-----------------------+-----------------------+---------+-------+-------+----------+-----------------------+
1 row in set, 2 warnings (0.01 sec)
停止匹配的条件
检索depno=1 and empname>'' and job=1的记录
据上面的图,这种检索方式只能先确定depno=1 and empname>''所在页的范围,然后对这个范围的所有页进行遍历,job字段在这个查询的过程中,是无法确定数据在哪些页的,此时我们说job是不走索引的,只有depno、empname能够有效的确定索引页的范围。
2.2.2 索引列使用函数
当我们不恰当的使用索引所对应的字段的时候,可能会导致索引失效,比如查询的过程没有保证独立的列,
这个独立的列是指索引对应的列不能作用在函数中。如下:
mysql> select * from emp where id = 4990000;
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| id | empno | empname | job | mgr | hiredate | sal | comn | depno |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| 4990000 | 4990000 | PWmulY | SALEMAN | 1 | 2021-01-23 16:46:24 | 2000 | 400 | 102 |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
1 row in set (0.002 sec)
mysql> select * from emp where ABS(id) = 4990001;
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| id | empno | empname | job | mgr | hiredate | sal | comn | depno |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| 4990001 | 4990001 | fXtdiH | SALEMAN | 1 | 2021-01-23 16:46:24 | 2000 | 400 | 107 |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
1 row in set (2.007 sec)
耗时分别是 0.002、2.007,使用explain分析后发现作用在函数的时候没有走索引,变成全表扫描:
mysql> explain select * from emp where id = 4990000;
+----+-------------+-------+-------+--------------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+--------------------+---------+---------+-------+------+-------+
| 1 | SIMPLE | emp | const | PRIMARY,idx_emp_id | PRIMARY | 4 | const | 1 | NULL |
+----+-------------+-------+-------+--------------------+---------+---------+-------+------+-------+
1 row in set
mysql> explain select * from emp where ABS(id) = 4990001;
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| 1 | SIMPLE | emp | ALL | NULL | NULL | NULL | NULL | 4952492 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
1 row in set
2.2.3 计算表达式导致索引无效
索引对应的列也不能作用于计算表达式中:
mysql> select * from emp where id = 4990000;
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| id | empno | empname | job | mgr | hiredate | sal | comn | depno |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| 4990000 | 4990000 | PWmulY | SALEMAN | 1 | 2021-01-23 16:46:24 | 2000 | 400 | 102 |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
1 row in set (0.002 sec)
mysql> select * from emp where id+1 = 4990001;
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| id | empno | empname | job | mgr | hiredate | sal | comn | depno |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| 4990000 | 4990000 | PWmulY | SALEMAN | 1 | 2021-01-23 16:46:24 | 2000 | 400 | 102 |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
1 row in set (1.762 sec)
耗时分别是 0.002、1.762,使用explain分析后发现作用在表达式的时候没有走索引,变成全表扫描:
mysql> explain select * from emp where id = 4990000;
+----+-------------+-------+-------+--------------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+--------------------+---------+---------+-------+------+-------+
| 1 | SIMPLE | emp | const | PRIMARY,idx_emp_id | PRIMARY | 4 | const | 1 | NULL |
+----+-------------+-------+-------+--------------------+---------+---------+-------+------+-------+
1 row in set
# 下面这种是不行的
mysql> explain select * from emp where id+1 = 4990001;
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| 1 | SIMPLE | emp | ALL | NULL | NULL | NULL | NULL | 4952492 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
1 row in set
# 下面这种是可以的
mysql> explain select * from emp where id = 4990001-1;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | emp | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
2.2.4 类型转换(自动或手动)导致索引失效
mysql> select * from emp where empname ='LsHfFJA';
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| id | empno | empname | job | mgr | hiredate | sal | comn | depno |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| 4582071 | 4582071 | LsHfFJA | SALEMAN | 1 | 2021-01-23 16:46:03 | 2000 | 400 | 106 |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
1 row in set
从这个数据中我们可以看出 empname为字符串类型的,depno为数值类型的,这两个上面都有独立的索引,我们来看两个语句:
mysql> select * from emp where empname =1;
Empty set, 65535 warnings (2.57 sec)
mysql> explain select * from emp where empname =1;
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | emp | NULL | ALL | NULL | NULL | NULL | NULL | 4982087 | 10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 2 warnings (0.00 sec)
mysql> select count(*) from emp where depno ='106';
+----------+
| count(*) |
+----------+
| 500195 |
+----------+
1 row in set (0.000 sec)
mysql> select count(*) from emp where depno =106;
+----------+
| count(*) |
+----------+
| 500195 |
+----------+
1 row in set (0.001 sec)
1、第一个查询,即便是在empname上建了索引,耗时依旧达到2s多。那是因为empname是字符串类型,字符串和数字比较的时候,会将字符串强制转换为数字,然后进行比较,所以整个查询变成了全表扫描,一个个抽出每条数据,将empname转换为数字和1进行比较。从第二个explain语句中也印证了这个算法。
2、 第三个和第四个查询,depno是int类型的,两个查询效率一致,都是正常利用索引快速检索。这是因为数值类型的字段,查询匹配的值无论是字符串还是数值都会走索引。
2.2.5 模糊查询(Like)左边包含%号
下面看两个查询,都采用了模糊查询,但是使用%开头会造成无法从页面确定扫描的位置,导致索引无效,继而全表扫描。
mysql> select * from emp where empname like 'LsHfFJA%';
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| id | empno | empname | job | mgr | hiredate | sal | comn | depno |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| 4582071 | 4582071 | LsHfFJA | SALEMAN | 1 | 2021-01-23 16:46:03 | 2000 | 400 | 106 |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
1 row in set (0.000 sec)
mysql> select * from emp where empname like '%LsHfFJA%';
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| id | empno | empname | job | mgr | hiredate | sal | comn | depno |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| 4582071 | 4582071 | LsHfFJA | SALEMAN | 1 | 2021-01-23 16:46:03 | 2000 | 400 | 106 |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
1 row in set (2.034 sec)
上面第一个查询可以利用到name字段上面的索引,下面的查询是无法确定需要查找的值所在的范围的,只能全表扫描,无法利用索引,所以速度比较慢,这个过程上面有说过。
2.2.6 条件使用or关键字(OR 前后存在非索引的列)
在 WHERE 子句中,OR 前后的条件列不属于索引列,那么索引会失效。
以下面的语句为例子,使用And就可以使用到索引,Or就会全表扫描。
原理其实很好理解,使用And的时候,我们在搜索树上先找到第一个条件字段(就是覆盖索引的depno),然后再缩小范围查找mgr字段。
但如果使用了Or,代表我的条件是两个,都得搜寻,才能找出所有数据。而未覆盖索引的那个条件依旧需要全表扫描。
# 语句1:是用到索引
mysql> explain select empno,empname,job from emp where depno=1 and mgr=1;
+----+-------------+-------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | emp | NULL | ref | idx_depno_empname_job | idx_depno_empname_job | 3 | const | 3705 | 10.00 | Using where |
+----+-------------+-------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
# 语句2:全表扫描
mysql> explain select empno,empname,job from emp where depno=1 or mgr=1;
+----+-------------+-------+------------+------+-----------------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+-----------------------+------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | emp | NULL | ALL | idx_depno_empname_job | NULL | NULL | NULL | 4982087 | 10.74 | Using where |
+----+-------------+-------+------------+------+-----------------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
# 语句3:全表扫描
mysql> explain select empno,empname,job from emp where mgr=1 or depno=7 ;
+----+-------------+-------+------------+------+-----------------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+-----------------------+------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | emp | NULL | ALL | idx_depno_empname_job | NULL | NULL | NULL | 4982087 | 10.74 | Using where |
+----+-------------+-------+------------+------+-----------------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
这种情况的优化方式就是在Or的时候两边列都覆盖入索引,就可以避免全表扫描
2.2.7 条件使用in关键字
in方式检索数据,我们还是经常用的。
平时我们做项目的时候,建议少用表连接,比如电商中需要查询订单的信息和订单中商品的名称,可以先查询查询订单表,然后订单表中取出商品的id列表,采用in的方式到商品表检索商品信息,由于商品id是商品表的主键,所以检索速度还是比较快的。
通过id在500万数据中检索100条数据,看看效果:
mysql> select * from emp a where
a.id in (800000, 800001, 800002, 800003, 800004, 800005, 800006, 800007, 800008, 800009, 800010, 800011, 800012, 800013,
800014, 800015, 800016, 800017, 800018, 800019, 800020, 800021, 800022, 800023, 800024, 800025, 800026, 800027, 800028,
800029, 800030, 800031, 800032, 800033, 800034, 800035, 800036, 800037, 800038, 800039, 800040, 800041, 800042, 800043, 800044,
800045, 800046, 800047, 800048, 800049, 800050, 800051, 800052, 800053, 800054, 800055, 800056, 800057, 800058, 800059, 800060,
800061, 800062, 800063, 800064, 800065, 800066, 800067, 800068, 800069, 800070, 800071, 800072, 800073, 800074, 800075, 800076,
800077, 800078, 800079, 800080, 800081, 800082, 800083, 800084, 800085, 800086, 800087, 800088, 800089, 800090, 800091, 800092,
800093, 800094, 800095, 800096, 800097, 800098, 800099);
+--------+--------+---------+---------+-----+---------------------+------+------+-------+
| id | empno | empname | job | mgr | hiredate | sal | comn | depno |
+--------+--------+---------+---------+-----+---------------------+------+------+-------+
| 800000 | 800000 | qVFqPY | SALEMAN | 1 | 2021-01-23 16:43:02 | 2000 | 400 | 105 |
| 800001 | 800001 | KVzJXL | SALEMAN | 1 | 2021-01-23 16:43:02 | 2000 | 400 | 107 |
| 800002 | 800002 | vWvpkj | SALEMAN | 1 | 2021-01-23 16:43:02 | 2000 | 400 | 102 |
............
| 800099 | 800099 | roxtAx | SALEMAN | 1 | 2021-01-23 16:43:02 | 2000 | 400 | 107 |
+--------+--------+---------+---------+-----+---------------------+------+------+-------+
100 rows in set (0.001 sec)
耗时1毫秒左右,还是相当快的。
这个相当于多个分解为多个唯一记录检索,然后将记录合并。所以这个其实也是快的,只要in里面的数据不是极端海量的即可。
单次查询选择的数据范围很大,比如占整个表的30%以上时,MySQL优化器可能会认为全表扫描比使用索引更快,因此选择不使用索引。这是因为全表扫描可能避免了索引查找和回表的开销,从而在某些情况下提供了更好的性能。
所以使用 in 在结果集中不能超过30%。
2.2.8 使用 not in 或 not exists
情况同于 2.2.7 节
2.2.9 条件中使用比较算法
如下代码:
- 第一个语句中使用了不等比较 (!= 或者<>) 导致索引失效,不等于需要所有索引数据拿出来比较,所以等同于全表扫描,也是慢的。
- 第二个语句中使用了比较符,虽然走索引,但是扫描数据超过30%,编译器会认为全表扫描性能比走索引更好,就不走索引了。这点可参考 2.2.7 节
- 第三个语句扫描的数据量(122054)远低于500w的30%,走索引。查询执行计划中包含Using index condition和Using MRR时,意味着正在使用高效的索引和存储引擎优化技术来加速查询。
# 使用不等比较(!= 或者<>) 导致索引失效
mysql> explain select * from emp where depno <> 7;
+----+-------------+-------+------------+------+-----------------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+-----------------------+------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | emp | NULL | ALL | idx_depno_empname_job | NULL | NULL | NULL | 4982087 | 52.45 | Using where |
+----+-------------+-------+------------+------+-----------------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
# 扫描数据量超30%,不走索引
mysql> explain select * from emp where depno > 7;
+----+-------------+-------+------------+------+-----------------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+-----------------------+------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | emp | NULL | ALL | idx_depno_empname_job | NULL | NULL | NULL | 4982087 | 50.00 | Using where |
+----+-------------+-------+------------+------+-----------------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
# 扫描数据量少,走索引
mysql> explain select * from emp where depno < 7;
+----+-------------+-------+------------+-------+-----------------------+-----------------------+---------+------+--------+----------+----------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+-----------------------+-----------------------+---------+------+--------+----------+----------------------------------+
| 1 | SIMPLE | emp | NULL | range | idx_depno_empname_job | idx_depno_empname_job | 3 | NULL | 122054 | 100.00 | Using index condition; Using MRR |
+----+-------------+-------+------------+-------+-----------------------+-----------------------+---------+------+--------+----------+----------------------------------+
1 row in set, 1 warning (0.00 sec)
3 总结
总结一下常见失效场景:
- 违反最左匹配原则
- 索引列使用函数计算
- 索引列使用计算表达式
- 索引列进行类型转换(自动或手动)
- 模糊查询(Like)左边包含%号
- 条件使用OR关键字,且在 OR 前后存在非索引的列
- 条件使用in关键字,且查询结果超过30%数据比
- 条件使用 not in 或 not exists,且查询结果超过30%数据比
- 条件中使用不等号(!= 或 <>)
除此之外,还有一些索引覆盖,规避回表的策略,我们后面的篇章再讨论。
MySQL系列:索引失效场景总结的更多相关文章
- 《MySQL面试小抄》索引失效场景验证
我是肥哥,一名不专业的面试官! 我是囧囧,一名积极找工作的小菜鸟! 囧囧表示:小白面试最怕的就是面试官问的知识点太笼统,自己无法快速定位到关键问题点!!! 本期主要面试考点 面试官考点之什么情况下会索 ...
- MySQL使用索引的场景及真正利用索引的SQL类型
1. 为什么使用索引 在无索引的情况下,MySQL会扫描整张表来查找符合sql条件的记录,其时间开销与表中数据量呈正相关.对关系型数据表中的某些字段建索引可以极大提高查询速度(当然,不同字段是否sel ...
- Mysql 索引失效场景
例如:一张USER表 有字段属性 name,age 其中name为索引 下面列举几个索引失效的情况 1. select * from USER where name=‘xzz’ or age= ...
- 索引之----mysql单列索引失效的情况
使用的索引名称: 1.隐式转换导致索引失效. 由于表字段定义为vachar类型,但在查询时把该字段作为number类型 以及where条件传给mysql. 2.对索引列进行任何操作(计算(+.-.*. ...
- 【MySQL】Mysql避免索引失效的情况有哪些
1.使用多列作为索引,则需要遵循最左前缀匹配原则(查询从索引的最左前列开始并且不跳过索引中的列) 2.不再索引列上做任何操作,例如(计算,函数,(自动 or 手动的类型转换)),会导致索引失效而转向全 ...
- MYSQL无法使用索引的场景
设计优化–无法使用索引的场景 •通过索引扫描的记录数超过30%,变成全表扫描 •联合索引中,第一个索引列使用范围查询--只能用到部分索引 •联合索引中,第一个查询条件不是最左索引列 •模糊查询条件列最 ...
- MySQL使用索引的场景分析、不能使用索引的场景分析
一.MySQL中能够使用索引的典型场景 1.匹配全值.对索引中的列都有等值匹配的条件.即使是在and中,and前后的列都有索引并进行等值匹配. 2.匹配值的范围查询,对索引的值能够进行范围查找. 3. ...
- MySQL索引失效之隐式转换
常见索引失效: 1. 条件索引字段"不干净":函数操作.运算操作 2. 隐式类型转换:字符串转数值:其他类型转换 3. 隐式字符编码转换:按字符编码数据长度大的方向转换,避免数据截 ...
- MySQL单列索引和组合索引的创建及区别介绍
MySQL单列索引是我们使用MySQL数据库中经常会见到的,MySQL单列索引和组合索引的区别可能有很多人还不是十分的了解,下面就为您分析两者的主要区别,供您参考学习. 为了形象地对比两者,再建一个表 ...
- 单点突破:MySQL之索引
前言 开发环境:MySQL5.7.31 什么是索引 在MySQL中,索引(Index)是帮助高效获取数据的数据结构. 我们可以将数据库理解为一本书,数据库中的各个数据列(column)就是目录中的章节 ...
随机推荐
- 【译】.NET 8 网络改进(二)
原文 | Máňa,Natalia Kondratyeva 翻译 | 郑子铭 修改 HttpClient 日志记录 自定义(甚至简单地关闭)HttpClientFactory 日志记录是长期请求的功能 ...
- NC15445 wyh的吃鸡
题目链接 题目 题目描述 最近吃鸡游戏非常火,你们wyh学长也在玩这款游戏,这款游戏有一个非常重要的过程,就是要跑到安全区内,否则就会中毒持续消耗血量,我们这个问题简化如下 假设地图为n*n的一个图, ...
- 对称加密算法汇总:AES DES 3DES SM4 java 实现入门
密码的世界 如果你是黑帮老大,平时和手下沟通,如何保证自己的信息安全呢? 在神探夏洛克的第一季中,就讲述了一个如何侦破黑帮的加密交流的故事. 这种密码利用的是密码字典. 密码本身可以是一本书,比如常见 ...
- 子集 II
子集 II 给定一个可能包含重复元素的整数数组nums,返回该数组所有可能的子集(幂集). 说明:解集不能包含重复的子集. 示例 输入: [1,2,2] 输出: [ [2], [1], [1,2,2] ...
- 识别主机名和IP地址
文章来源:https://oracle-base.com/articles/misc/identifying-host-names-and-addresses Identifying Host Nam ...
- Java并发编程实例--4.控制线程打断
Java提供了InterruptedException异常,当我们检测到线程被打断时可以抛出并在run()方法中进行捕捉. 本例中,我们将开发一个程序以实现根据文件名称在指定文件夹(包括其子目录)中搜 ...
- QT - Day 3
对话框 分类 模态对话框 QDialog dlg(this); dlg.resize(200,100); dlg.exec(); //窗口阻塞 非模态对话框 QDialog *dlg2 = new Q ...
- python列表操作的大O效率
- context讲解
context包 context包介绍 在go语言中,每个独立调用一般都会被单独的协程处理.但在处理一个请求时,往往可能需要在多个协程之间进行信息传递,甚至包括一层层地递进顺序传递,而且这种信息往 ...
- OPPO 自研大规模知识图谱及其在数智工程中的应用
导读:OPPO 知识图谱是 OPPO 数智工程系统小布助手团队主导.多团队协作建设的自研大规模通用知识图谱,目前已达到数亿实体和数十亿三元组的规模,主要落地在小布助手知识问答.电商搜索等场景. 本文主 ...