只有 7 种。在这种成为 坑 的列值比较少的情况下,可以考虑用 IN 来填补这个 坑 从而形成最左前缀:
EXPLAIN SELECT*FROM employees.titles WHERE emp_no='10009' AND title IN ('Senior Engineer', 'Staff', 'Engineer', 'Senior Staff', 'Assistant Engineer', 'Technique Leader', 'Manager') AND from_date='1995-02-18';
+----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type |table| partitions | type | possible_keys | key | key_len |ref|rows| filtered | Extra | +----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ |1| SIMPLE | titles |NULL|range|PRIMARY|PRIMARY|159|NULL|7|100.00|Usingwhere| +----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
这次 key_len 为 159,说明索引被用全了,但是从 type 和 rows 看出 IN 实际上执行了一个 range 查询,这里检查了 7 个 key。看下两种查询的性能比较:
SET profiling =1; SELECT* FROM... -- 1 SELECT* FROM... -- 2 SHOW PROFILES;
EXPLAIN SELECT*FROM employees.titles WHERE emp_no <'10010' AND title ='Senior Engineer' AND from_date BETWEEN'1986-01-01'AND'1986-12-31';
+----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type |table| partitions | type | possible_keys | key | key_len |ref|rows| filtered | Extra | +----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ |1| SIMPLE | titles |NULL|range|PRIMARY|PRIMARY|4|NULL|14|1.11|Usingwhere| +----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
可以看到索引对第二个范围索引无能为力。这里特别要说明 MySQL 一个有意思的地方,那就是仅用 explain 可能无法区分 范围索引 和 多值匹配,因为在 type 中这两者都显示为 range。
同时,用了 between 并不意味着就是范围查询,例如下面的查询:
EXPLAIN SELECT*FROM employees.titles WHERE emp_no BETWEEN'10001'AND'10010' AND title='Senior Engineer' AND from_date BETWEEN'1986-01-01'AND'1986-12-31';
+----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type |table| partitions | type | possible_keys | key | key_len |ref|rows| filtered | Extra | +----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ |1| SIMPLE | titles |NULL|range|PRIMARY|PRIMARY|159|NULL|15|1.11|Usingwhere| +----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
看起来是用了两个范围查询,但作用于 emp_no 上的 BETWEEN 实际上相当于 IN,也就是说 emp_no 实际是多值精确匹配。可以看到这个查询用到了索引全部三个列。因此在 MySQL 中要谨慎地区分多值匹配和范围匹配,否则会对 MySQL 的行为产生困惑。