索引在哪些场景下会失效,通过测试来验证一下。
新建一张表,初始化了 30 万条数据进去。

1
2
3
4
5
6
7
8
9
CREATE TABLE employees (
emp_no INT NOT NULL,
birth_date DATE NOT NULL,
first_name VARCHAR(14) NOT NULL,
last_name VARCHAR(16) NOT NULL,
gender ENUM ('M','F') NOT NULL,
hire_date DATE NOT NULL,
PRIMARY KEY (emp_no)
);

首先我们给这张表添加一个索引,就使用 first_name 字段好了

1
ALTER TABLE employees ADD INDEX employees_n1 ( first_name );

当我们使用 first_name 字段去查询时,索引是生效的

1
2
3
4
5
6
7
8

mysql> EXPLAIN SELECT * FROM employees WHERE first_name = 'Mary';
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | employees | NULL | ref | employees_n1 | employees_n1 | 16 | const | 224 | 100.00 | NULL |
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

那在什么场景下才会失效呢? 以下是可能导致索引失效的原因:

隐式或显式类型转换

first_name 是一个 Varchar 类型的字段,我们使用的值 123 是整数类型,Mysql 会尝试将 first_name 字段中的值转成数字类型,从而导致该索引字段失效

1
2
3
4
5
6
mysql> EXPLAIN SELECT * FROM employees WHERE first_name = 123;
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | employees | NULL | ALL | employees_n1 | NULL | NULL | NULL | 299556 | 10.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+

对索引字段进行运算

下面例子中对 first_name 字段进行了运算导致索引失效

1
2
3
4
5
6
mysql> EXPLAIN SELECT * FROM employees WHERE LEFT(first_name,1) = 'Mary';
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | employees | NULL | ALL | NULL | NULL | NULL | NULL | 299556 | 100.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+

LIKE 左模糊匹配

下面例子中分别对索引字段 first_name 使用 Like 进行匹配,可以发现当出现左模糊匹配时,索引失效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mysql> EXPLAIN SELECT * FROM employees WHERE first_name LIKE '%Mary%';
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | employees | NULL | ALL | NULL | NULL | NULL | NULL | 299556 | 11.11 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> EXPLAIN SELECT * FROM employees WHERE first_name LIKE '%Mary';
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | employees | NULL | ALL | NULL | NULL | NULL | NULL | 299556 | 11.11 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> EXPLAIN SELECT * FROM employees WHERE first_name LIKE 'Mary%';
+----+-------------+-----------+------------+-------+---------------+--------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+-------+---------------+--------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | employees | NULL | range | employees_n1 | employees_n1 | 16 | NULL | 224 | 100.00 | Using index condition |
+----+-------------+-----------+------------+-------+---------------+--------------+---------+------+------+----------+-----------------------+

OR 连接非索引字段

使用索引字段查询的同时使用 OR 连接一个非索引字段时,会导致该索引字段失效。
但如果改成使用 AND 则不影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> EXPLAIN SELECT * FROM employees WHERE first_name = 'Mary' OR last_name = 'Sluis';
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | employees | NULL | ALL | employees_n1 | NULL | NULL | NULL | 299556 | 10.07 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+

mysql> EXPLAIN SELECT * FROM employees WHERE first_name = 'Mary' AND last_name = 'Sluis';
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | employees | NULL | ref | employees_n1 | employees_n1 | 16 | const | 224 | 10.00 | Using where |
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------+------+----------+-------------+

最左匹配原则

在使用组合索引时,条件中必须出现组合索引中的第一个字段,该索引才会生效
下面我们删除原来的索引,新建一个组合索引

1
2
3
4
5
6
7
8
mysql> ALTER TABLE employees DROP INDEX employees_n1;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql> ALTER TABLE employees ADD INDEX employees_n1 ( first_name, last_name, gender );
Query OK, 0 rows affected (1.35 sec)
Records: 0 Duplicates: 0 Warnings: 0

下面的测试语句中:
第一条语句的条件包含了三个字段,分别都是组合索引中的字段,因此索引是生效的。

1
2
3
4
5
6
7
mysql> EXPLAIN SELECT * FROM employees WHERE first_name = 'Mary' AND last_name = 'Sluis' AND gender = 'M';
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------------------+------+----------+-------+
| 1 | SIMPLE | employees | NULL | ref | employees_n1 | employees_n1 | 35 | const,const,const | 1 | 100.00 | NULL |
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

第二条语句中,把组合索引中的第一个字段去除了,此时索引没有生效。

1
2
3
4
5
6
7
8

mysql> EXPLAIN SELECT * FROM employees WHERE last_name = 'Sluis' AND gender = 'M';
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | employees | NULL | ALL | NULL | NULL | NULL | NULL | 299556 | 5.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+

第三条语句中,把条件的位置更换了,没有按组合索引中的字段顺序来查询,此时索引也是生效的。

1
2
3
4
5
6
7
8

mysql> EXPLAIN SELECT * FROM employees WHERE last_name = 'Sluis' AND first_name = 'Mary' AND gender = 'M';
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------------------+------+----------+-------+
| 1 | SIMPLE | employees | NULL | ref | employees_n1 | employees_n1 | 35 | const,const,const | 1 | 100.00 | NULL |
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------------------+------+----------+-------+

第四条语句中,把组合索引中间的字段去除,只加了 first_name 与 gender 两个条件,虽然看起来也走了索引,但实际上 gender 条件索引是失效的,只有 first_name 字段进行了索引

1
2
3
4
5
6
7
mysql> EXPLAIN SELECT * FROM employees WHERE first_name = 'Mary' AND gender = 'M';
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | employees | NULL | ref | employees_n1 | employees_n1 | 16 | const | 224 | 50.00 | Using index condition |
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------+------+----------+-----------------------+

因此组合索引优化可以考虑:

  1. 最好包含所有索引字段
  2. 条件中包含索引中的第一个字段
  3. 尽量不要跳过中间的字段

比较运算后面的索引失效

为了方便进行比较运算,重新建一下索引

1
2
3
4
5
6
7
mysql> ALTER TABLE employees DROP INDEX employees_n1;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql> ALTER TABLE employees ADD INDEX employees_n1 ( first_name, birth_date, last_name );
Query OK, 0 rows affected (1.26 sec)
Records: 0 Duplicates: 0 Warnings: 0

下面例子中:
第一条语句是三个条件都走了索引
第二个语句把中间字段使用了大小比较运算符,导致后面的字段索引失效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> EXPLAIN SELECT * FROM employees WHERE first_name = 'Mary' AND birth_date = '1958-02-26' AND last_name = 'Sluis';
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------------------+------+----------+-------+
| 1 | SIMPLE | employees | NULL | ref | employees_n1 | employees_n1 | 37 | const,const,const | 1 | 100.00 | NULL |
+----+-------------+-----------+------------+------+---------------+--------------+---------+-------------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> EXPLAIN SELECT * FROM employees WHERE first_name = 'Mary' AND birth_date > '1958-02-26' AND last_name = 'Sluis';
+----+-------------+-----------+------------+-------+---------------+--------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+-------+---------------+--------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | employees | NULL | range | employees_n1 | employees_n1 | 19 | NULL | 111 | 10.00 | Using index condition |
+----+-------------+-----------+------------+-------+---------------+--------------+---------+------+------+----------+-----------------------+