尼恩说在前面
mysql索引失效,主要场景是什么? mysql索引失效,如何解决?
最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,后台回复:领电子书
本文目录
- 尼恩说在前面
- 1. 准备工作:造50W数据
- 首先创建一张测试表 test_user表
- 创建函数:随机产生字符串,给身份证、用户名字段使用
- 创建函数:用于随机整数,年龄字段使用
- 使用存储过程,插入50W条测试数据
- 监控MySQL存储过程执行时间
- 等待执行结束
- 查看执行计划
- 2. 破坏 最左匹配 原则,导致 索引失效
- 什么是联合索引的 最左匹配原则?
- 2.1 联合索引的场景:哪些情况,索引是有效的呢?
- 第一种情况,查询 id_card
-`type=ref` 的含义
- 第2种情况,查询 id_card 和 age
- 第3种情况,查询 id_card 和 age 和 user_name
- 第4种情况,查询 id_card 和 user_name
- "Extra = Using index condition" 的具体介绍
- Using index condition 与 "Using index" 的区别
- 最左匹配原则
- 2.2 联合索引的场景:哪些情况,索引是失效的呢?
- 第1种情况,跳过第一列,查询age
- 重点:索引扫描和全表扫描的区别
- 第2种情况,跳过第1和第2列,查询user_name
- 第3种情况,跳过第1列,查询age,user_name
- 3. 破坏 索引覆盖 原则,导致的 索引失效
- 什么是索引覆盖?
- 使用了select * 破坏索引覆盖,导致索引失效
- 使用select 索引列,满足 索引覆盖,避免索引失效
- 4. 破坏了 前缀匹配原则,导致 索引失效
- 什么是 前缀匹配?
- 例子:使用前缀匹配 进行 模糊查询
- 例子2:破坏了模糊查询的 前缀匹配 ,
- 例子3:后缀匹配的例子
- 例子4:中间匹配的例子
- 5. order by 排序不当,导致的索引失效
- 场景1:索引列 和 ORDER BY 列不匹配
- 场景2:使用 SELECT *
- 场景3:ORDER BY 与 WHERE 子句不匹配
- 总结:order by 排序导致的索引失效的解决方案
- 6. or关键字使用不当,导致索引失效
- 场景1:OR跨越了多个列,而没有建立复合索引
- 场景2:范围查询与等值查询的 OR 组合
- 总结:or关键字使用不当,导致索引失效解决方法:
- 7. 索引列上有计算或者函数,导致的索引失效
- 场景1:索引列上有计算,导致的索引失效
- 场景2:索引列上有函数,导致的索引失效
- 索引列上有计算或者函数,导致的索引失效如何解决
- 8. 使用 not in和not exists不当,导致索引失效
- 8.1 使用 `NOT IN` 不当,导致索引失效
- `type = range` 的含义:
- 使用 NOT EXISTS 导致索引失效 的原因
- 8.2 使用 `NOT EXISTS` 不当,导致索引失效
- 使用 NOT EXISTS 导致索引失效 的原因
- 9. 其他场景,如:列的比对,导致索引失效
- 说在最后:有问题找老架构取经
1. 准备工作:造50W数据
首先创建一 张测试表 test_user表 为function指定一个参数 创建函数:随机产生字符串 创建函数:用于随机产生多少到多少的编号 创建存储过程:往test_user表中插入50万条数据 查看结果
首先创建一张测试表 test_user表
id
、id_card
、age
、user_name
和height
、address字段。CREATE TABLE `test_user` (
`id` int NOT NULL AUTO_INCREMENT,
`id_card` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
`age` int DEFAULT '0',
`user_name` varchar(30) COLLATE utf8mb4_bin DEFAULT NULL,
`height` int DEFAULT '0',
`address` varchar(30) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_id_card_age_user_name` (`id_card`,`age`,`user_name`),
KEY `idx_height` (`height`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
聚族索引 id
:数据库的聚族索引联合索引 idx_id_card_age_user_name
:由id_card、age和user_name三个字段组成的联合索引。非聚族索引 idx_height
:普通索引
创建函数:随机产生字符串,给身份证、用户名字段使用
DELIMITER $$
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
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 $$
#假如要删除
#drop function rand_string;
[Err] 1418 - This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)
SET GLOBAL log_bin_trust_function_creators = 1;
创建函数:用于随机整数,年龄字段使用
DELIMITER $$
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num -from_num+1)) ;
RETURN i;
END$$
#假如要删除 函数
#drop function rand_num;
使用存储过程,插入50W条测试数据
DELIMITER $$
CREATE PROCEDURE insert_test_user( START INT , max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
#set autocommit =0 把autocommit设置成0
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO test_user (`id`, `id_card` ,`age` ,`user_name` , `height` ,`address` ) VALUES ((START+i) ,rand_string(18) , rand_num(30,50),rand_string(10), rand_num(100,180),rand_string(6) );
UNTIL i = max_num
END REPEAT;
COMMIT;
END$$
#删除
DELIMITER ;
drop PROCEDURE insert_test_user;
# 执行存储过程,往insert_test_user表添加50万条数据
DELIMITER ;
CALL insert_test_user(1,500000);
监控MySQL存储过程执行时间
CREATE TABLE proc_execution_time (
id INT AUTO_INCREMENT PRIMARY KEY,
proc_name VARCHAR(255),
start_time DATETIME,
end_time DATETIME,
execution_time INT
);
DELIMITER $$
CREATE PROCEDURE insert_test_user( START INT , max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
DECLARE start_time DATETIME;
DECLARE end_time DATETIME;
-- 记录开始时间
SET start_time = NOW();
-- set autocommit =0 把autocommit设置成0
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO test_user (`id`, `id_card` ,`age` ,`user_name` , `height` ,`address` ) VALUES ((START+i) ,rand_string(18) , rand_num(30,50),rand_string(10), rand_num(100,180),rand_string(6) );
UNTIL i = max_num
END REPEAT;
-- 记录结束时间
SET end_time = NOW();
-- 计算执行时间
-- 这里我们使用函数TIMESTAMPDIFF来计算两个时间的差值
SET @execution_time := TIMESTAMPDIFF(SECOND, start_time, end_time);
-- 将执行时间记录到监控表
INSERT INTO proc_execution_time (`proc_name`, `start_time`, `end_time`, `execution_time`)
VALUES ('test_01', start_time, end_time, @execution_time);
COMMIT;
END$$
# 执行存储过程,往insert_test_user表添加50万条数据
DELIMITER ;
CALL insert_test_user(1,500000);
等待执行结束
查看执行计划
explain
关键字,查看该sql语句的执行计划,来判断索引使用情况。explain select * from test_user where id=1;
主键索引
。2. 破坏 最左匹配 原则,导致 索引失效
id_card age user_name
什么是联合索引的 最左匹配原则?
A
, B
, C
的复合索引 (A, B, C)
,那么这个索引的匹配规则遵循“最左匹配原则”,即 MySQL 会优先从最左边的列开始依次使用索引。全匹配 (满足最左匹配原则):
SELECT * FROM table WHERE A = 1 AND B = 2 AND C = 3;
(A, B, C)
索引来加速查询。部分匹配:
匹配第一列 (满足最左匹配原则):
SELECT * FROM table WHERE A = 1;
A
,仍然可以有效利用 (A, B, C)
复合索引。匹配前两列 (满足最左匹配原则):
SELECT * FROM table WHERE A = 1 AND B = 2;
A
和 B
,MySQL 会利用索引进行查询加速。匹配第1列,中间有跳列 (满足最左匹配原则):
SELECT * FROM table WHERE A = 1 AND C = 3;
A
和 C
,跳过 B
,MySQL 会利用联合索引 进行查询加速。跳过第一列的匹配: 如果跳过了索引中的第一列,MySQL 将无法利用索引的剩余部分。比如:
SELECT * FROM table WHERE B = 2 AND C = 3;
A
,MySQL 无法使用复合索引 (A, B, C)
来加速这个查询。范围查询的影响: 在使用范围查询(如 <
、>
、BETWEEN
、LIKE
等)时,复合索引只会匹配到范围查询前的部分。例如:
SELECT * FROM table WHERE A = 1 AND B > 2 AND C = 3;
A
和 B
列进行过滤,但对于 C
列,无法继续利用索引进行优化,因为 B
是一个范围查询。2.1 联合索引的场景:哪些情况,索引是有效的呢?
第一种情况,查询 id_card
explain select * from test_user where id_card='rDUKToLQUcDJMJTAVe';
key= idx_id_card_age_user_name
type=ref
type=ref 的含义
非唯一索引匹配:
ref
表示 MySQL 使用了索引,但这个索引并不是唯一的。即在查询时,可能会找到多条符合条件的记录,而不仅仅是一条。这种情况常见于查询条件中使用了非唯一索引(例如普通索引或复合索引中的一部分列),因此查询返回的结果可能包含多行数据。
type=ref
适用于那些匹配某个索引列的单个值或前缀查询。例如,假设有一个索引(A, B)
,查询语句使用了A
列的条件,那么 MySQL 会使用A
列上的索引来过滤数据,访问类型为ref
。
EXPLAIN
输出中,type=ref
是一种查询访问类型,表示 MySQL 使用了非唯一索引(或前缀索引)进行查询,能够通过匹配某个索引的某些列来查找数据。这通常比 ALL
(全表扫描)和 index
(全索引扫描)更高效,但不如 const
或 eq_ref
等类型的性能更好。第2种情况,查询 id_card 和 age
explain select * from test_user where id_card='rDUKToLQUcDJMJTAVe' and age=43
key= idx_id_card_age_user_name
type=ref
第3种情况,查询 id_card 和 age 和 user_name
explain select * from test_user where id_card='rDUKToLQUcDJMJTAVe' and age=43 and user_name='dryssKwdbY';
key= idx_id_card_age_user_name
type=ref
第4种情况,查询 id_card 和 user_name
explain select * from test_user where id_card='rDUKToLQUcDJMJTAVe' and user_name='dryssKwdbY';
key= idx_id_card_age_user_name
type=ref
Extra = Using index condition
"Extra = Using index condition" 的具体介绍
Using index condition 与 "Using index" 的区别
Using index:表示查询可以完全通过索引来获取数据,不需要访问数据表的行(即 "索引覆盖")。这种情况性能较好,因为只访问了索引,没有读表。 Using index condition:表示 MySQL 只使用了索引进行部分过滤,但仍然需要读取表中的数据行。虽然通过索引进行了一定的优化,但相比完全使用索引,性能会略逊一筹。
最左匹配原则
最左匹配原则
。2.2 联合索引的场景:哪些情况,索引是失效的呢?
跳过第一列的匹配: 如果跳过了索引中的第一列,MySQL 将无法利用索引的剩余部分。比如:
SELECT * FROM table WHERE B = 2 AND C = 3;
A
,MySQL 无法使用复合索引 (A, B, C)
来加速这个查询。第1种情况,跳过第一列,查询age
explain select * from test_user where age=43 ;
key= null
type= all
Extra = Using where
Extra = Using where
是一个表示查询优化器正在有效利用WHERE
子句的条件来优化查询执行的标志。重点:索引扫描和全表扫描的区别
system:系统表,少量数据,往往不需要进行磁盘IO const:常量连接 eq_ref:主键索引(primary key)或者非空唯一索引(unique not null)等值扫描 ref:非主键非唯一索引等值扫描 range:范围扫描 index:索引树扫描 ALL:全表扫描(full table scan)
索引树扫描可以从树根往下 做类似的 二分查找, 时间复杂度是 o(logn); 全表扫描 是扫描所有的 叶子节点, 时间复杂度是 o(n); 全表扫描不仅仅需要扫描索引列,还需要扫描每个索引列中指向的实际数据,这里包含了所有的非索引列数据。
第2种情况,跳过第1和第2列,查询user_name
explain select * from test_user where user_name='dryssKwdbY' ;
第3种情况,跳过第1列,查询age,user_name
explain select * from test_user where age=43 and user_name='dryssKwdbY' ;
跳过第一列的匹配: 如果跳过了索引中的第一列,MySQL 将无法利用索引的剩余部分。比如:
SELECT * FROM table WHERE B = 2 AND C = 3;
A
,MySQL 无法使用复合索引 (A, B, C)
来加速这个查询。3. 破坏 索引覆盖 原则,导致的 索引失效
什么是索引覆盖 ?
尼恩用一句话简单来说:
索引覆盖:查询的字段全部在索引的字段中。
employees
,包含以下列:employee_id
(主键)name
age
department
idx_name_age
包含name
和age
列。SELECT name, age FROM employees WHERE name = '尼恩';
idx_name_age
已经包含了查询所需的name
和age
列,MySQL可以直接使用这个索引来获取数据,而不需要回表查询。EXPLAIN
查询计划的输出中,如果一个查询实现了索引覆盖,你通常会看到Using index
的提示。减少I/O操作:由于不需要访问表的数据行,因此减少了磁盘I/O操作,这对于磁盘存储的数据库来说尤其重要。 提高查询速度:索引通常比表数据更加紧凑,并且是经过优化的,所以使用索引覆盖可以加快查询速度。 减少数据访问量:数据库引擎只需要读取索引,而不需要读取整个表,这减少了数据访问量。
使用了select * 破坏索引覆盖,导致索引失效
explain select * from test_user where user_name='dryssKwdbY' ;
key= null
type= all
Extra = Using where
Extra = Using where
是一个表示查询优化器正在有效利用WHERE
子句的条件来优化查询执行的标志。select *
,从执行结果看,走了全表扫描,没有用到任何索引,查询效率是非常低的。select *
查询所有列的数据,大概率会查询非索引列的数据,非索引列不会走索引,查询效率非常低。select *
。使用select 索引列,满足 索引覆盖,避免索引失效
SELECT
查询时,如果只选择那些被索引覆盖的列,可以避免索引失效并实现索引覆盖。users
的表,它有以下结构:CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100),
age INT,
email VARCHAR(100)
);
name
和 email
列:CREATE INDEX idx_name_email ON users(name, email);
SELECT name, email FROM users WHERE name = 'John Doe';
name
和 email
都在 idx_name_email
索引中,数据库引擎可以直接使用这个索引来获取数据,而不需要全表扫描。明确指定需要的列:不要使用 SELECT *
,而是只选择那些被索引覆盖的列。创建合适的索引:确保你的索引包含了查询中需要的所有列。 使用 EXPLAIN 分析查询:在MySQL中,你可以使用 EXPLAIN
关键字来分析查询是否会使用覆盖索引。如果Extra
列中包含Using index
,那么查询就是索引覆盖的。
explain select id,age,user_name from test_user where user_name='dryssKwdbY' ;
key= idx_id_card_age_user_name
type= index
Extra = Using where; Using index
WHERE
条件需要在索引过滤之后,对结果集进行进一步的过滤。4. 破坏了 前缀匹配原则,导致 索引失效
什么是 前缀匹配?
索引前缀匹配: MySQL 可以利用 B-Tree 索引高效查找字符串前缀匹配的记录。例如,给定一个建立在字段 name
上的 B-Tree 索引,以下查询可以利用到索引:SELECT * FROM users WHERE name LIKE 'John%'; 这里的 LIKE 'John%'
会匹配以 "John" 开头的所有字符串。索引适用于左前缀: MySQL 的索引结构适用于从左到右的顺序查找。 因此,只能利用字符串从左侧开始的前缀进行匹配,比如 LIKE 'prefix%'
,而不能利用%Suffix
或%Suffix%
这样的查询。这是因为后者要求扫描整个表来找到匹配项,不符合索引的顺序结构。复合索引的前缀匹配: 当查询中涉及多个列时,如果使用复合索引,MySQL 会根据列的顺序从左至右依次匹配前缀。 只有符合索引定义顺序的最左部分才能被利用。例如,如果有索引 (col1, col2)
,只有以下查询可以利用该索引:SELECT * FROM table WHERE col1 = 'value1' AND col2 LIKE 'value2%'; 如果 col1
没有出现在查询条件中,索引将无法使用。限制前缀长度的索引: MySQL 允许在某些数据类型(如 VARCHAR
、TEXT
)上建立前缀长度索引,例如对前 10 个字符建立索引。前缀索引可以减少索引大小,但只适合前缀查询。
例子:使用前缀匹配 进行 模糊查询
CREATE INDEX idx_user_name ON test_user (user_name);
select * from test_user where user_name like 'a%';
explain select * from test_user where user_name like 'a%';
key= idx_user_name
type= index
Extra = Using index condition
EXPLAIN
的输出中看到 Extra
列包含 Using index condition
时,这意味着:MySQL 正在使用索引条件推送优化查询。 部分查询条件在索引层面被评估,而不是在索引查找之后回表进行评估。 这通常意味着查询优化器认为在索引层面进行过滤比回表过滤更高效。
Using index condition
是 MySQL 优化查询性能的一种方式,它通过减少不必要的回表操作来提高查询效率。例子2:破坏了模糊查询的 前缀匹配
例子3:后缀匹配的例子
explain select * from test_user where user_name like '%a';
key= null
type= all
Extra = Using where
Extra = Using where
是一个表示查询优化器正在有效利用WHERE
子句的条件来优化查询执行的标志。例子4:中间匹配的例子
explain select * from test_user where user_name like '%a%';
key= null
type= all
Extra = Using where
Extra = Using where
是一个表示查询优化器正在有效利用WHERE
子句的条件来优化查询执行的标志。like
语句中的%
,出现在查询条件的左边时,索引会失效。如果相同,再匹对左边第二个字母, 如果再相同匹对其他的字母,以此类推。
5. order by 排序不当,导致的索引失效
order by
就能搞定。ORDER BY
子句通常用于根据一个或多个列对结果集进行排序。ORDER BY
,它可能会导致索引失效,从而影响查询性能。ORDER BY
导致索引失效的情况和相应的解决方案:索引列和 ORDER BY 列不匹配: 如果 ORDER BY
子句中引用的列没有被索引覆盖,MySQL 可能无法使用索引来排序,而必须进行额外的排序操作(Using filesort
)。为了解决这个问题,可以创建一个包含 ORDER BY
列的索引。使用 SELECT *: 使用 SELECT *
可能会导致索引失效,因为查询返回了所有列,而不仅仅是索引列。这可能会迫使数据库进行额外的行查找以获取非索引列的数据。 ORDER BY 与 WHERE 子句不匹配: 如果 WHERE
子句中的条件列和ORDER BY
子句中的排序列不一致,且没有相应的索引,那么索引可能不会被使用。ORDER BY 子句中的列没有索引的最左列:如果 ORDER BY
子句中的列没有索引的最左列或不遵循最左匹配原则,索引可能不会被使用。ORDER BY 子句中的列使用了函数或表达式:对列进行函数操作或计算可能会阻止 MySQL 使用索引。 ORDER BY 子句中的列有 DESC 和 ASC 混合使用:如果 ORDER BY
子句中既有升序又有降序的排序,可能会引起Using filesort
。
场景1:索引列 和 ORDER BY 列不匹配
explain select * from test_user order by id_card, height;
key= null
type= all
Extra = Using filesort
如果,排序的内容比较小,那么,在内存中就可以搞定,这就是内部排序(使用快排); 如果,要排序的内容太大,那么,就得需要通过磁盘的帮助了,这个就是外部排序(使用归并)。
如果,一行的内容不是很大,那么,就整个字段读取出来进行排序,称为全字段排序; 如果,整个字段内容很大,那么,就采用rowid排序,读取rowid和该字段先进行排序,然后,再回表查找其他的内容;
场景2:使用 SELECT *
SELECT *
可能会导致索引失效,因为查询返回了所有列,而不仅仅是索引列。场景3:ORDER BY 与 WHERE 子句不匹配
WHERE
子句中的条件列和 ORDER BY
子句中的排序列不一致,且没有相应的索引,那么索引可能不会被使用。explain select height from test_user where id_card like '%a%' order by height;
ORDER BY
子句中的列被索引覆盖。ORDER BY
子句的例子explain select age from test_user where id_card like '%a%' order by age;
总结:order by 排序导致的索引失效的解决方案
创建合适的索引,确保 ORDER BY
子句中的列被索引覆盖。避免使用 SELECT *
,只选择必要的列。使用 FORCE INDEX
或USE INDEX
来强制查询使用特定的索引。重新设计查询,以确保 ORDER BY
子句中的列可以有效地使用索引。
EXPLAIN
)来确定是否确实需要优化索引策略。6. or关键字使用不当,导致索引失效
or
关键字的场景非常多,但如果你稍不注意,就可能让已有的索引失效。OR
关键字用于组合多个条件,使得只要满足其中之一的条件就会被选中。OR
使用不当,可能会导致索引失效,从而影响查询性能。OR
使用不当导致索引失效的情况和解决方法:多个列的 OR 条件: 如果 OR
条件跨越了多个列,而没有建立复合索引,那么索引可能不会被使用。例如,如果有两个列 A
和B
,而查询条件是A = x OR B = y
,且只有单独在A
或B
上建立的索引,那么索引可能不会被使用。范围查询与等值查询的 OR 组合: 对于像 A > x OR B = y
这样的条件,如果A
列的索引是针对范围查询优化的,而B
列的索引是针对等值查询优化的,MySQL 可能无法同时使用这两个索引。
场景1:OR跨越了多个列,而没有建立复合索引
explain select age from test_user where id_card like '%a%' OR height=180;
key= null
type= all
Extra = Using where
or
关键字,那么它前面和后面的字段都要加索引,不然所有的索引都会失效,这是一个大坑。场景2:范围查询与等值查询的 OR 组合
explain select id_card from test_user where id_card = 'hSQBimMXEbwbSlCdKr' OR height>160;
key= null
type= all
Extra = Using where
总结:or关键字使用不当,导致索引失效解决方法:
使用复合索引: 如果可能,创建一个包含所有 OR
条件列的复合索引。这样,MySQL 可以更有效地使用索引来处理查询。 拆分查询: 将包含 OR
条件的查询拆分成多个独立的查询,然后使用UNION
或UNION ALL
来合并结果。这样每个查询都可以独立地使用其相关的索引。 SELECT * FROM table WHERE A = x
UNION
SELECT * FROM table WHERE B = y;使用 FORCE INDEX: 在某些情况下,可以使用 FORCE INDEX
来强制查询使用特定的索引,尽管这可能会限制查询优化器的灵活性。调整查询逻辑: 重新设计查询逻辑,尽量减少使用 OR
,或者将OR
条件转换为等价的IN
子句或其他可以更有效使用索引的形式。
7. 索引列上有计算或者函数,导致的索引失效
age
,并且在查询中使用 age * 1.5
或 DATE(birthday)
,索引将不会被使用。场景1:索引列上有计算,导致的索引失效
explain select * from test_user where id +1 =10000 ;
key= null
type= all
Extra = Using where
场景2:索引列上有函数,导致的索引失效
explain select * from test_user where ceil(id) =10000 ;
key= null
type= all
Extra = Using where
索引列上有计算或者函数,导致的索引失效如何解决
避免在索引列上使用计算和函数:重写查询,将计算和函数移出索引列。例如,如果可能,可以在应用层进行计算,或者使用已经计算好的列(如果适用)。 使用合适的索引:如果某些函数操作是不可避免的,考虑创建一个计算列并为其建立索引。例如,如果经常需要根据 DATE(birthday)
来查询,可以创建一个存储日期的计算列并为其建立索引。
8. 使用 not in和not exists不当,导致索引失效
in exists not in not exists between and
NOT IN
和 NOT EXISTS
子句通常用于排除某些记录,如果不正确使用这些子句,它们可能会导致索引失效,从而影响查询性能。NOT IN
和 NOT EXISTS
导致索引失效的情况和解决方法:8.1 使用 NOT IN 不当,导致索引失效
-- 没有用到了 索引
explain select * from test_user where height not in (173,174,175,176);
key= null
type= all
Extra = Using where
-- 用到了 索引
explain select * from test_user where height in (173,174,175,176);
key= idx_height
type= range
Extra = Using index condition
type = range 的含义:
范围查询: range
表示 MySQL 正在使用索引来检索位于某个范围内的行。这通常发生在使用BETWEEN...AND...
、>
、<
、>=
、<=
或LIKE
(当模式以通配符%
结尾时除外)等操作符时。索引扫描: range
表明 MySQL 正在执行索引扫描,这是介于全表扫描(type = ALL
)和索引查找(如type = ref
或type = eq_ref
)之间的一种访问方法。它比全表扫描更高效,但可能不如直接索引查找快。
explain select * from test_user where id not in (173,174,175,176);
key= PRIMARY
type= range
Extra = Using where
使用 NOT EXISTS 导致索引失效 的原因
NOT IN
可能导致 MySQL 索引失效的主要原因在于其处理方式与 MySQL 的优化器机制存在冲突,尤其是在处理大数据集和 NULL
值时。具体原因如下:NULL
值,MySQL 的行为会变得不确定。NULL
表示未知值,无法确定一个值是否“不在”包含 NULL
的集合中。NOT IN
查询在遇到 NULL
时会停止利用索引。subquery
的结果集包含 NULL
,索引将无法有效利用:SELECT * FROM table1 WHERE id NOT IN (SELECT id FROM table2);
table2.id
包含 NULL
时,MySQL 无法正确处理,并且可能会选择全表扫描来确保结果正确性。NOT IN
操作,优化器有时会认为全表扫描比使用索引更高效,尤其当需要过滤大量数据时。
SELECT * FROM users WHERE id NOT IN (1, 2, 3);
id
列有索引,但如果 NOT IN
的结果集包含很多数据,优化器可能认为使用索引不是最佳选择,从而直接进行全表扫描。explain select * from test_user force index(idx_height) where height not in (173,174,175,176)
key= idx_height
type= range
Extra = Using index condition
NOT IN
本质上是排除某些匹配项的操作,而排除操作相较于直接匹配操作,计算成本通常更高。8.2 使用 NOT EXISTS 不当,导致索引失效
SELECT * FROM customers WHERE NOT EXISTS (SELECT 1 FROM orders WHERE orders.customer_id = customers.customer_id);
explain select * from test_user t1 where not exists (select 1 from test_user t2 where t2.height=178 and t1.id_card=t2.id_card)
考虑重写查询,使用 LEFT JOIN
来替代NOT EXISTS
。
explain select * from test_user t1 LEFT JOIN test_user t2 on t1.id_card=t2.id_card where t2.height=178 and t2.id_card IS NULL;
使用 NOT EXISTS 导致索引失效 的原因
NOT EXISTS
查询时可能导致索引失效,NOT EXISTS
的方式和索引匹配机制存在一定的差异。反向逻辑操作的处理
NOT EXISTS
本质上是在查询某个子查询中不存在某些记录。SELECT * FROM table1 WHERE NOT EXISTS (
SELECT * FROM table2 WHERE table1.id = table2.id
);
table2
,逐行判断是否存在与 table1.id
匹配的记录。table2
的某列存在索引,但由于 NOT EXISTS
的反向操作,MySQL 通常无法高效利用索引来做排除式判断。NULL 值的影响
NOT EXISTS
在处理与 NULL
相关的记录时可能遇到问题。NULL
值,MySQL 可能无法使用索引,原因是索引通常不包含 NULL
值。优化器选择
NOT EXISTS
的条件时,它可能会放弃使用索引。9. 其他场景,如:列的比对,导致索引失效
explain select * from test_user where id=height
key= null
type= all
Extra = Using where
说在最后:有问题找老架构取经
被裁之后, 空窗1年/空窗2年, 如何 起死回生 ?
案例1:42岁被裁2年,天快塌了,急救1个月,拿到开发经理offer,起死回生
案例2:35岁被裁6个月, 职业绝望,转架构急救上岸,DDD和3高项目太重要了
案例3:失业15个月,学习40天拿offer, 绝境翻盘,如何实现?
被裁之后,100W 年薪 到手, 如何 人生逆袭?
100W案例,100W年薪的底层逻辑是什么? 如何实现年薪百万? 如何远离 中年危机?
如何 逆天改命,包含AI、大数据、golang、Java 等
实现职业转型,极速上岸
关注职业救助站公众号,获取每天职业干货
助您实现职业转型、职业升级、极速上岸
---------------------------------
实现架构转型,再无中年危机
关注技术自由圈公众号,获取每天技术千货
一起成为牛逼的未来超级架构师
几十篇架构笔记、5000页面试宝典、20个技术圣经
请加尼恩个人微信 免费拿走
暗号,请在 公众号后台 发送消息:领电子书
如有收获,请点击底部的"在看"和"赞",谢谢