先从最朴素的查询开始
假设我们有个超级简单的表 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
就能秒懂啦!