假设有以下表结构:
CREATE TABLE test (
id INT NOT NULL,
last_name CHAR(30) NOT NULL,
first_name CHAR(30) NOT NULL,
PRIMARY KEY (id),
INDEX name (last_name,first_name)
);
索引 name 是 last_name 列和 first_name 列组合的索引,该索引既可以用于查询 last_name 和 first_name 的组合条件,也可以用于单独查询 last_name。
SELECT * FROM test WHERE last_name='Jones';
SELECT * FROM test
WHERE last_name='Jones' AND first_name='John';
SELECT * FROM test
WHERE last_name='Jones'
AND (first_name='John' OR first_name='Jon');
SELECT * FROM test
WHERE last_name='Jones'
AND first_name >='M' AND first_name < 'N';
这就是索引的最左前缀原则,如果表具有多列索引,例如 (col1, col2, col3),那么 (col1) 、 (col1, col2) 和 (col1, col2, col3) 的查询都可以用到该索引。相反的,(col2, col3) 或 (col3) 则不会用到,例如:
SELECT * FROM test WHERE first_name='John';
SELECT * FROM test
WHERE last_name='Jones' OR first_name='John';
第一条很好理解,first_name 显然不在最左边,但是第二条就比较奇怪了,last_name 明明是符合最左前缀原则的,但是官方文档写的是不适用于 name 索引。
这里直接使用 mysql 官方提供的测试数据库 employees,因为官方的示例中数据库字段只有 first_name 和 last_name,但是 employees 数据库里远不止这些字段,所以我这里模拟下实际情况,只查这两个字段:
EXPLAIN SELECT
first_name,last_name
FROM
employees
WHERE
last_name = 'Cairo' or first_name = 'Wanqing'
可以看到,这里的 type 是 index,说明 mysql 扫描了整个索引树,并且走了索引,这么看来 mysql 文档好像是错的?也不一定,看第二个示例:
SELECT
*
FROM
employees
WHERE
last_name = 'Cairo' or first_name = 'Wanqing'
在这种情况,explain 的 type 直接是 ALL,并且也没有用索引。
出现这种情况的原因如下:
- 在第一种情况是,只查询了 last_name 和 first_name,而这两列都包含在索引当中,所以优化过后直接扫描索引更快
- 在第二种情况中,first_name 显然是不能走索引的,且要查询的是所有字段,其次可能又因为 first_name 的选择性不高,mysql 优化器就优化为全表扫描了。
# 参考
- MySQL 5.7 Reference Manual - 8.3.5 Multiple-Column Indexes
- 掘金 - 最左前缀原则