Skip to content

mysql索引失效的常见场景

约 2882 字大约 10 分钟

2025-05-18

测试数据库版本

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.4.5     |
+-----------+
1 row in set (0.01 sec)

sql分析

参见文章: SQL执行分析

测试数据表

说明

不要关注索引的科学性与合理性, 此处的索引仅仅是为了掩饰索引失效情况而存在

CREATE TABLE `people` (
    `id` bigint unsigned NOT NULL AUTO_INCREMENT,
    `code` varchar(20)  COLLATE  utf8mb4_bin  NOT NULL DEFAULT  '',
    `age`  int unsigned  NOT NULL DEFAULT  '0',
    `name`  varchar(30)  COLLATE  utf8mb4_bin  NOT NULL DEFAULT  '',
    `height`  int unsigned  NOT NULL DEFAULT  '0',
    `address`  varchar(30)  COLLATE  utf8mb4_bin  NOT NULL DEFAULT  '',
    PRIMARY  KEY  (`id`),
    KEY  `idx_code_age_name`  (`code`,`age`,`name`),
    KEY  `idx_height`  (`height`)
)  ENGINE=InnoDB  AUTO_INCREMENT=1  DEFAULT  CHARSET=utf8mb4  COLLATE=utf8mb4_bin;
INSERT INTO people (id, code, age, name, height, address) VALUES (1, '101', 21, '周星驰', 175, '香港'), (2, '102', 18, '周杰伦', 173, '台湾'), (3, '103', 23, '苏三', 174, '成都');

不满足最左匹配原则

explain select * from people where code = '100000' and name = '张三';
explain select * from people where code = '100000' and age > 10;
explain select * from people where code = '100000' and age > 10 and name = '张三';

以上三条sql语句 会命中索引, 均是索引 idx_code_age_name

+----+-------------+--------+------------+-------+-------------------+-------------------+---------+------+------+----------+-----------------------+
| id | select_type | table  | partitions | type  | possible_keys     | key               | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+--------+------------+-------+-------------------+-------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | people | NULL       | range | idx_code_age_name | idx_code_age_name | 86      | NULL |    1 |   100.00 | Using index condition |
+----+-------------+--------+------------+-------+-------------------+-------------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.04 sec)
explain select * from people where name = '张三';
explain select * from people where age = 10;
explain select * from people where name = '张三';

以上三条sql语句 不会命中索引, 均是全表扫描, sql 分析结果如下:

+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | people | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

总结

上述示例命中索引的sql语句中都有code字段,它是索引字段中的第一个字段,也就是 最左边的字段。只要有这个字段在,该sql已经就能走索引。这就是我们所说的最左匹配原则。

隐式类型转换

上面说了, 只要查询语句中出现 code 作为查询条件, 满足了最左前缀规则, 就可以使用索引. 但是这是全部场景么? 并不是的.

看一下下面的sql语句:

explain select * from people where code = 100000;

sql语句中, code字段的值是一个int数字, 但是索引字段是varchar类型, 这是一个隐式类型转换. 所以sql语句 不会命中索引, sql分析结果如下:

mysql> explain select * from people where code = 100000;
+----+-------------+--------+------------+------+-------------------+------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys     | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+------+-------------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | people | NULL       | ALL  | idx_code_age_name | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+--------+------------+------+-------------------+------+---------+------+------+----------+-------------+
1 row in set, 3 warnings (0.01 sec)

将sql修改成 explain select * from people where code = '100000'; 便可以命中索引了. 在日常使用过程中, 隐式类型转换导致索引失效的情况还是比较多的.

那么, 隐式类型转换是否一定到值索引失效? 不是的, 如果索引不是联合索引, 有独立字段构成的索引, 是可以命中的

mysql> explain select * from people where height="234";
+----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-------+
| id | select_type | table  | partitions | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | people | NULL       | ref  | idx_height    | idx_height | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

但是隐式类型转换需要注意有可能查出非预期数据, 这个适合mysql内部类型转换实现有关, 具体转换参见: mysql类型转换规则

查出非预期数据示例:

mysql> select * from people where height="175";
+----+------+-----+-----------+--------+---------+
| id | code | age | name      | height | address |
+----+------+-----+-----------+--------+---------+
|  1 | 101  |  21 | 周星驰    |    175 | 香港    |
+----+------+-----+-----------+--------+---------+
1 row in set (0.00 sec)

mysql> select * from people where height="175s";
+----+------+-----+-----------+--------+---------+
| id | code | age | name      | height | address |
+----+------+-----+-----------+--------+---------+
|  1 | 101  |  21 | 周星驰    |    175 | 香港    |
+----+------+-----+-----------+--------+---------+
1 row in set, 1 warning (0.00 sec)

select *

使用了select *, 会 导致无法使用任何覆盖索引, 尽可能按需查询需要的字段, 在查询条件无法使用任何索引的情况下, 如果查询的字段全部在某一个索引中, 则会使用覆盖索引

explain select * from people where name = 'zhangsan';
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | people | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

下面则会命中覆盖索引:

explain select code, name from people where name ='zhangsan';
+----+-------------+--------+------------+-------+-------------------+-------------------+---------+------+------+----------+--------------------------+
| id | select_type | table  | partitions | type  | possible_keys     | key               | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+--------+------------+-------+-------------------+-------------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | people | NULL       | index | idx_code_age_name | idx_code_age_name | 208     | NULL |    1 |   100.00 | Using where; Using index |
+----+-------------+--------+------------+-------+-------------------+-------------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

上述sql分析结果中, 发现命中了索引, 在Extra中, 发现除了 Use Where 之外, 多了 Using index 信息, 说明, 查询条件本身不满足任何索引, 但是查询的字段全部在索引中, 所以使用了覆盖索引.

索引列上有计算

explain select * from user where id  = 1;

上面的sql可以说是日常使用频率做高的了, 基于主键索引查询数据详情, 但是如果对 id 进行计算, 则会导致索引失效:

mysql> explain select * from people where id - 1 = 1;
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | people | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |   100.00 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

对id字段进行计算之后, 发现逐渐索引已失效, 无法命中索引, 那么是不是完全无法再sql语句中进行运算呢? 也不是的, 可以将运算移到右边, 在表达式中计算.

mysql> explain select * from people where id = 1 + 1;
+----+-------------+--------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table  | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+--------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | people | NULL       | const | PRIMARY       | PRIMARY | 8       | const |    1 |   100.00 | NULL  |
+----+-------------+--------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

将 id - 1 = 1 调整为 id = 1 + 1, 发现依旧可以使用逐渐索引

那么, 如果表达式中存在字段呢?

mysql> explain select * from people where id = age + 1;
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | people | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |    33.33 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

发现也是无法命中索引的

说明

索引列上有计算, 指的是在字段上(=左侧)不要有计算逻辑, 但是表达式上可以有计算逻辑(=右侧), 如果计算逻辑中包含其他字段,也是无法命中索引

索引列上使用函数

explain select * from people where height=17; 因为在 height 列上有索引, 所以一定能命中索引

explain select * from people where substr(height,1,2)=17;
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | people | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |   100.00 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

上述在height列上使用函数之后发现索引失效了, 在上面介绍在索引列上计算会导致索引失效, 但是将计算逻辑全部放到表达式中, 是可以命中索引的, 那么使用函数是不是形同逻辑呢?

explain select * from people where height=substr(age, 1,2);
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | people | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |    33.33 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

函数作用在表达式上, 但是表达式中包含字段, 是无法命中索引的, 那么如果表达式中不包含字段, 是纯粹的函数运算呢?

mysql> explain select * from people where height=substr(123, 1,2);
+----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-------+
| id | select_type | table  | partitions | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | people | NULL       | ref  | idx_height    | idx_height | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

发现上述是可以命中索引的

总结

合在列上进行运算类似, 索引使用函数, 无法命中索引; 在表达式中使用函数, 如果表达式不包含其他字段, 则可以命中索引, 如果包含其他字段, 则无法命中索引

like左侧模糊匹配

模糊查询,在我们日常的工作中,使用频率还是比较高的。

explain select * from people where height like "%1%";
explain select * from people where height like "%1";
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | people | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |    33.33 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

上述两条sql均无法命中索引, 而下面的sql, 是可以命中索引的

mysql> explain select * from people where height like "1%";
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | people | NULL       | ALL  | idx_height    | NULL | NULL    | NULL |    3 |    33.33 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

总结

like 语句有些类似于联合索引的最左前缀匹配, 只有不存在左%的时候才会使用索引

列对比

列对比是指对数据库两个字段进行比较, 上面说过的 对索引列进行运算/使用函数, 其实就是列对比, 此种场景索引失效, 无法使用。

OR语句

先来看如下查询语句, 是否能命中索引

explain select * from people where id = 1 or height = 180;

结果是可以命中, 分析结果如下:

mysql> explain select * from people where id = 1 or height = 180;
+----+-------------+--------+------------+-------------+--------------------+--------------------+---------+------+------+----------+----------------------------------------------+
| id | select_type | table  | partitions | type        | possible_keys      | key                | key_len | ref  | rows | filtered | Extra                                        |
+----+-------------+--------+------------+-------------+--------------------+--------------------+---------+------+------+----------+----------------------------------------------+
|  1 | SIMPLE      | people | NULL       | index_merge | PRIMARY,idx_height | PRIMARY,idx_height | 8,4     | NULL |    2 |   100.00 | Using union(PRIMARY,idx_height); Using where |
+----+-------------+--------+------------+-------------+--------------------+--------------------+---------+------+------+----------+----------------------------------------------+
1 row in set, 1 warning (0.00 sec)

但是不是说 OR 语句会导致索引失效么? 那再看如下语句:

explain select * from people where id = 1 or address = '北京';

这个语句能命中索引么? 答案是索引失效

mysql> explain select * from people where id = 1 or address = '北京';
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | people | NULL       | ALL  | PRIMARY       | NULL | NULL    | NULL |    3 |    55.56 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

为何第一个场景的sql能命中索引而第二个场景的SQL无法命中索引?

原因是第一个sql语句, id与height均具有索引, 但是第二个sql语句中, address没有索引。可以再自行尝试下如下sql是否命中索引

explain select * from people where id = 1 or code = '123';

总结

OR语句并不是一定会导致索引失效,但是命中索引的前提是: 每一个or条件都能命中索引

NOT IN / NOT EXISTS

以前的时候, NOT IN 会导致索引失效, 但是 测试用的 mysql8 是可以命中索引的

mysql> explain select * from people where height not in (123, 456);
+----+-------------+--------+------------+-------+---------------+------------+---------+------+------+----------+-----------------------+
| id | select_type | table  | partitions | type  | possible_keys | key        | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+--------+------------+-------+---------------+------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | people | NULL       | range | idx_height    | idx_height | 4       | NULL |    5 |   100.00 | Using index condition |
+----+-------------+--------+------------+-------+---------------+------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

ORDER BY

注意, ORDER BY 可以正常使用索引的, 但是排序字段会影响排序效率:

mysql> explain select * from people where code = "123"  order by name;
+----+-------------+--------+------------+------+-------------------+-------------------+---------+-------+------+----------+----------------+
| id | select_type | table  | partitions | type | possible_keys     | key               | key_len | ref   | rows | filtered | Extra          |
+----+-------------+--------+------------+------+-------------------+-------------------+---------+-------+------+----------+----------------+
|  1 | SIMPLE      | people | NULL       | ref  | idx_code_age_name | idx_code_age_name | 82      | const |    1 |   100.00 | Using filesort |
+----+-------------+--------+------------+------+-------------------+-------------------+---------+-------+------+----------+----------------+
1 row in set, 1 warning (0.01 sec)

上述sql命中了索引, 但是order by的时候,排序效率比较低,需要走一次filesort排序

mysql> explain select * from people where code = "123"  order by code,age,name;
mysql> explain select * from people where code = "123"  order by code,age;
+----+-------------+--------+------------+------+-------------------+-------------------+---------+-------+------+----------+-------+
| id | select_type | table  | partitions | type | possible_keys     | key               | key_len | ref   | rows | filtered | Extra |
+----+-------------+--------+------------+------+-------------------+-------------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | people | NULL       | ref  | idx_code_age_name | idx_code_age_name | 82      | const |    1 |   100.00 | NULL  |
+----+-------------+--------+------------+------+-------------------+-------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

上述sql没有需要额外排序

mysql> explain select * from people where code = "123"  order by code,height;
+----+-------------+--------+------------+------+-------------------+-------------------+---------+-------+------+----------+----------------+
| id | select_type | table  | partitions | type | possible_keys     | key               | key_len | ref   | rows | filtered | Extra          |
+----+-------------+--------+------------+------+-------------------+-------------------+---------+-------+------+----------+----------------+
|  1 | SIMPLE      | people | NULL       | ref  | idx_code_age_name | idx_code_age_name | 82      | const |    1 |   100.00 | Using filesort |
+----+-------------+--------+------------+------+-------------------+-------------------+---------+-------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)

上述sql也经过filesort排序了

mysql> explain select * from people where code = "123"  order by code asc , age desc, name asc;
+----+-------------+--------+------------+------+-------------------+-------------------+---------+-------+------+----------+----------------+
| id | select_type | table  | partitions | type | possible_keys     | key               | key_len | ref   | rows | filtered | Extra          |
+----+-------------+--------+------------+------+-------------------+-------------------+---------+-------+------+----------+----------------+
|  1 | SIMPLE      | people | NULL       | ref  | idx_code_age_name | idx_code_age_name | 82      | const |    1 |   100.00 | Using filesort |
+----+-------------+--------+------------+------+-------------------+-------------------+---------+-------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)

上述sql也经过了额外的filesort排序

总结

查询条件条件能命中索引, ORDER BY 就可以正常走索引, 但是 ORDER BY 的规则会影响排序的效率:

  • ORDER BY 字段与查询条件命中统一索引, 排序效率最好, 戴氏瑶瑶所有字段同升(asc)或者同降(desc)
  • 其他情况排序时会走一次filesort排序, 效率会有所降低

Released under the MIT License.