先从最朴素的查询开始
假设我们有个超级简单的表 users,里面存了用户信息:
sql
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(100)
);
INSERT INTO users (id, name, email) VALUES
(1, 'Alice', 'alice@example.com'),
(2, 'Bob', 'bob@example.com'),
(3, 'Charlie', 'charlie@example.com');
现在我们跑个最简单的查询:
sql
EXPLAIN SELECT * FROM users WHERE id = 1;
结果大概长这样:
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | Using index |
别慌,咱们先搞懂 select_type 和 type 是啥。
select_type:查询的类型
select_type 告诉你这条查询在整个执行计划里是个什么角色。简单来说,它描述的是查询的"身份"。常见的值有:
- SIMPLE :最简单的查询,没有子查询、没有 UNION。比如上面这个例子,就是
SIMPLE,因为它就是一个单表查询。 - PRIMARY :如果是多表查询或者有子查询,最外层的那个查询会被标记为
PRIMARY。 - SUBQUERY :子查询,比如
WHERE id IN (SELECT id FROM another_table)里的子查询。 - UNION :多个查询用 UNION 合并时,第二个及之后的查询会被标记为
UNION。
回到上面的例子,select_type 是 SIMPLE,因为这只是一个单表、无子查询的简单查找。后面我们会看到更复杂的例子。
type:访问方式
type 是重头戏,它告诉你 MySQL 是怎么从表里捞数据的,也就是访问方式。性能从高到低大概是这样排序的:const > eq_ref > ref > range > index > ALL。咱们一个一个讲,顺便配上例子。
1. const
- 啥意思:MySQL 通过主键或者唯一索引直接定位到一行数据,效率贼高。
- 例子 :上面的
WHERE id = 1,因为id是主键,MySQL 直接找到那行,结果就是const。 - 形象记忆:就像你在家门口放了个信箱,地址写得清清楚楚,邮递员直接塞进去,不用翻遍整个小区。
2. eq_ref
-
啥意思:通常在联表查询(JOIN)中,通过主键或唯一索引精确匹配一行。
-
例子 : 假设还有个表
orders:sqlCREATE TABLE orders ( order_id INT PRIMARY KEY, user_id INT, FOREIGN KEY (user_id) REFERENCES users(id) ); INSERT INTO orders (order_id, user_id) VALUES (101, 1), (102, 2);查询:
sqlEXPLAIN SELECT u.name, o.order_id FROM users u JOIN orders o ON u.id = o.user_id;对于
orders表,type会是eq_ref,因为user_id通过主键id一对一匹配。 -
结果表 :
id select_type table type possible_keys key rows Extra 1 SIMPLE u ALL PRIMARY NULL 3 1 SIMPLE o eq_ref user_id user_id 1 -
形象记忆:就像你拿身份证去银行柜台办业务,柜员直接通过你的唯一号码查到你。
3. ref
-
啥意思:通过非唯一索引查找,可能返回多行。
-
例子 : 给
users的name加个普通索引:sqlCREATE INDEX idx_name ON users(name); EXPLAIN SELECT * FROM users WHERE name = 'Alice';因为
name不是唯一的,type是ref。 -
结果表 :
id select_type table type possible_keys key rows Extra 1 SIMPLE users ref idx_name idx_name 1 -
形象记忆:就像你在学校找叫"Alice"的同学,可能有好几个,得挨个问。
4. range
-
啥意思 :通过索引做范围查找,比如
>、<、BETWEEN。 -
例子 :
sqlEXPLAIN SELECT * FROM users WHERE id BETWEEN 1 AND 2;type是range,因为用主键查了个范围。 -
结果表 :
id select_type table type possible_keys key rows Extra 1 SIMPLE users range PRIMARY PRIMARY 2 Using where -
形象记忆:就像你在超市找价格在 10 到 20 块之间的商品,得扫一眼货架。
5. index
-
啥意思:扫描整个索引树,比全表扫描好点,但还是不够快。
-
例子 :
sqlEXPLAIN SELECT name FROM users;如果只查索引覆盖的字段,
type是index。 -
形象记忆:像翻电话簿,虽然有索引,但得从头翻到尾。
6. ALL
-
啥意思:全表扫描,最慢的方式。
-
例子 :
sqlEXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';没索引,
type是ALL。 -
结果表 :
id select_type table type possible_keys key rows Extra 1 SIMPLE users ALL NULL NULL 3 Using where -
形象记忆:像大海捞针,一行一行找。
朴素策略的问题
从上面的例子看,最朴素的查询(比如 ALL)效率低得可怕。假设 users 表有 100 万行数据,全表扫描得扫 100 万次,时间成本和资源消耗都爆炸。更别提多表联查了,如果两个表都全扫描,复杂度直接平方级别上升。
不利因子:
- 没索引:MySQL 只能挨个儿看每行,像盲人摸象。
- 范围不明确 :即使有索引,如果条件太模糊(比如
email LIKE '%alice%'),还是得扫描大量数据。 - 联表效率低:没有合适的键关联,JOIN 操作会变成噩梦。
优化方向:逼近主流方案
基于这些问题,优化思路其实跟现在的数据库设计不谋而合:
-
加索引:
- 主键和唯一索引让
const和eq_ref成为可能。 - 普通索引支持
ref和range,大幅减少扫描行数。 - 示例:给
email加索引,type从ALL变成ref。
- 主键和唯一索引让
-
覆盖索引:
- 查询只涉及索引字段时,
type可以是index,避免回表。 - 示例:
SELECT name FROM users用idx_name覆盖。
- 查询只涉及索引字段时,
-
优化联表:
- 用外键或索引确保 JOIN 用上
eq_ref或ref。 - 示例:
orders.user_id加索引,JOIN 时效率飞升。
- 用外键或索引确保 JOIN 用上
-
条件收窄:
- 用精确条件(
=,IN)替代模糊匹配(LIKE '%...%'),让range更高效。
- 用精确条件(
这些优化方向正是现代数据库的标配,比如 B+ 树索引、联合索引、主从架构,都是为了把扫描范围缩到最小,把 type 尽量往 const、eq_ref 靠拢。
总结
select_type 是查询的身份标签,type 是捞数据的具体手法。咱们从最简单的 SIMPLE 和 const 开始,逐步看到 ALL 的低效,再通过索引和条件优化逼近高效方案。记住这些形象比喻,下次看 explain 就能秒懂啦!