索引失效
- 索引列存在类型转换
- 索引列使用了函数
- 索引列参与了运算
- 模糊查询最前面的为不确定匹配字符
- 索引列使用IS NULL或者IS NOT NULL
- 联合索引不满足最左匹配原则
- 使用了select *
- 使用OR条件,但不是所有的条件都有索引
- 使用不等于(!= 或者 <>)操作符
- 使用order by时
- 列上的普通索引 not in和not exists
数据准备
mysql
CREATE TABLE `t_userinfo` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
`id_no` varchar(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '身份编号',
`username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `union_idx` (`id_no`,`username`,`age`),
KEY `create_time_idx` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
INSERT INTO `t_userinfo` (`id`, `id_no`, `username`, `age`, `create_time`) VALUES (null, '1001', 'Tom1', 11, '2022-02-27 09:04:23');
INSERT INTO `t_userinfo` (`id`, `id_no`, `username`, `age`, `create_time`) VALUES (null, '1002', 'Tom2', 12, '2022-02-26 09:04:23');
INSERT INTO `t_userinfo` (`id`, `id_no`, `username`, `age`, `create_time`) VALUES (null, '1003', 'Tom3', 13, '2022-02-25 09:04:23');
INSERT INTO `t_userinfo` (`id`, `id_no`, `username`, `age`, `create_time`) VALUES (null, '1004', 'Tom4', 14, '2023-02-25 09:04:23');
-- 删除历史存储过程
DROP PROCEDURE IF EXISTS `insert_t_user`
-- 创建存储过程
delimiter $
CREATE PROCEDURE insert_t_user(IN limit_num int)
BEGIN
DECLARE i INT DEFAULT 10;
DECLARE id_no varchar(18) ;
DECLARE username varchar(32) ;
DECLARE age TINYINT DEFAULT 1;
WHILE i < limit_num DO
SET id_no = CONCAT("NO", i);
SET username = CONCAT("Tom",i);
SET age = FLOOR(10 + RAND()*2);
INSERT INTO `t_userinfo` VALUES (NULL, id_no, username, age, NOW());
SET i = i + 1;
END WHILE;
END $
-- 调用存储过程
call insert_t_user(100);
索引失效场景
1、索引列存在类型转换
id_no 数据库的类型为varchar
,但是查询添加的参数类型为整数。在查询过程中发生了隐式类型转换,导致索引失效。
mysql
mysql> explain select * from t_userinfo where id_no = 1002;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_userinfo | NULL | ALL | union_idx | NULL | NULL | NULL | 94 | 10.00 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 3 warnings (0.01 sec)
mysql>
这种情况还有一个特例,如果字段类型为int类型,而查询条件添加了单引号或双引号,则Mysql会参数转化为int类型,虽然使用了单引号或双引号。这种情况还是会走索引的。
mysql
mysql> explain select * from t_userinfo where id = '2';
+----+-------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t_userinfo | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql>
2、索引列使用了函数
mysql
mysql> explain select * from t_userinfo where SUBSTR(id_no , 1, 3) = '100';
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_userinfo | NULL | ALL | NULL | NULL | NULL | NULL | 94 | 100.00 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql>
类似的函数还有很多,如:concat
等。
3、索引列参与了运算
mysql
# id列参与了计算
mysql> explain select * from t_userinfo where id + 1 = 2;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_userinfo | NULL | ALL | NULL | NULL | NULL | NULL | 94 | 100.00 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql>
# 在参数侧计算,id列没有参与计算
mysql> explain select * from t_userinfo where id = 2 - 1;
+----+-------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t_userinfo | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql>
4、模糊查询最前面的为不确定匹配字符
mysql
mysql> explain select * from t_userinfo where id_no like '%3';
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_userinfo | NULL | ALL | NULL | NULL | NULL | NULL | 94 | 11.11 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql>
模糊匹配的占位符位于条件的首部 导致索引失效
5、索引列使用IS NULL或者IS NOT NULL
mysql
mysql> explain select * from t_userinfo where id_no is not null;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_userinfo | NULL | ALL | union_idx | NULL | NULL | NULL | 94 | 100.00 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql>
6、联合索引不满足最左匹配原则
使用到的联合索引KEY
union_idx (
id_no,
username,
age)
- 最左边的字段为id_no,只要保证id_no出现在查询条件中,则会走该联合索引。
mysql
mysql> explain select * from t_userinfo where id_no = '1001';
+----+-------------+------------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t_userinfo | NULL | ref | union_idx | union_idx | 75 | const | 1 | 100.00 | NULL |
+----+-------------+------------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql>
mysql> explain select * from t_userinfo where id_no = '1001' and username = 'Tom1';
+----+-------------+------------+------------+------+---------------+-----------+---------+-------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+-----------+---------+-------------+------+----------+-------+
| 1 | SIMPLE | t_userinfo | NULL | ref | union_idx | union_idx | 206 | const,const | 1 | 100.00 | NULL |
+----+-------------+------------+------------+------+---------------+-----------+---------+-------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from t_userinfo where username = 'Tom1' and id_no = '1001';
+----+-------------+------------+------------+------+---------------+-----------+---------+-------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+-----------+---------+-------------+------+----------+-------+
| 1 | SIMPLE | t_userinfo | NULL | ref | union_idx | union_idx | 206 | const,const | 1 | 100.00 | NULL |
+----+-------------+------------+------------+------+---------------+-----------+---------+-------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql>
- 不满足最左匹配原则,失效的情况
mysql
mysql> explain select * from t_userinfo where username = 'Tom1';
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_userinfo | NULL | ALL | NULL | NULL | NULL | NULL | 94 | 10.00 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql>
mysql> explain select * from t_userinfo where age = 11;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_userinfo | NULL | ALL | NULL | NULL | NULL | NULL | 94 | 10.00 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql>
7、使用了select *
在上面的联合索引中,如果查询条件是age或username,当使用了select *
,肯定是不会走索引的
mysql
mysql> explain select * from t_userinfo where age = 11;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_userinfo | NULL | ALL | NULL | NULL | NULL | NULL | 94 | 10.00 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql>
# 查询列明确指定需要查询的列
mysql> explain select id,id_no,username,age from t_userinfo where age = 11;
+----+-------------+------------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | t_userinfo | NULL | index | union_idx | union_idx | 211 | NULL | 94 | 10.00 | Using where; Using index |
+----+-------------+------------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select id,id_no,username,age from t_userinfo where username = 'Tom1';
+----+-------------+------------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | t_userinfo | NULL | index | union_idx | union_idx | 211 | NULL | 94 | 10.00 | Using where; Using index |
+----+-------------+------------+------------+-------+---------------+-----------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)
mysql>
8、使用OR条件,但不是所有的条件都有索引
mysql
# 虽然id列有索引,但是username列不满足左匹配原则,使用条件OR,不满足所有添加都有索引
mysql> explain select * from t_userinfo where id = '2' or username = 'Tom1';
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_userinfo | NULL | ALL | PRIMARY | NULL | NULL | NULL | 94 | 19.00 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
# 两个列都满足有索引,即使是使用条件OR,也会走索引
mysql> explain select * from t_userinfo where id = '2' or id_no = '1002';
+----+-------------+------------+------------+-------------+-------------------+-------------------+---------+------+------+----------+--------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------------+-------------------+-------------------+---------+------+------+----------+--------------------------------------------------+
| 1 | SIMPLE | t_userinfo | NULL | index_merge | PRIMARY,union_idx | union_idx,PRIMARY | 75,4 | NULL | 2 | 100.00 | Using sort_union(union_idx,PRIMARY); Using where |
+----+-------------+------------+------------+-------------+-------------------+-------------------+---------+------+------+----------+--------------------------------------------------+
1 row in set, 1 warning (0.01 sec)
mysql>
9、使用不等于(!= 或者 <>)操作符
当查询条件为字符串时,使用"<>"或"!="作为条件查询,不走索引
mysql> explain select * from t_userinfo where id_no <> '1002';
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_userinfo | NULL | ALL | union_idx | NULL | NULL | NULL | 94 | 98.94 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql>
但是如何字段类型为int的,还是可以走索引的。
mysql> explain select * from t_userinfo where id <> '1002';
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_userinfo | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 95 | 100.00 | Using where |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql>
10、使用order by时
mysql> explain select * from t_userinfo order by id_no asc;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | t_userinfo | NULL | ALL | NULL | NULL | NULL | NULL | 94 | 100.00 | Using filesort |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)
mysql>
11、列上的普通索引 not in和not exists
因为认为 not in 时结果集会比较大,而 in 的时候结果集会比较小。
但是not in 查询的是主键字段,还是会走索引的,普通索引就会失效。
mysql
mysql> explain select * from t_userinfo where id_no not in('1002');
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_userinfo | NULL | ALL | union_idx | NULL | NULL | NULL | 94 | 98.94 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from t_userinfo where id_no in('1002');
+----+-------------+------------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t_userinfo | NULL | ref | union_idx | union_idx | 75 | const | 1 | 100.00 | NULL |
+----+-------------+------------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql>