7.7 KiB
7.7 KiB
解读 EXPLAIN 结果的关键字段
以 MySQL 为例,重要的列包括:
| 列名 | 说明 | 优化关注点 |
|---|---|---|
| type | 访问类型 | 最好到最差:system > const > eq_ref > ref > range > index > ALL |
| key | 实际使用的索引 | 检查是否使用了预期索引 |
| rows | 预计扫描行数 | 数值越小越好 |
| Extra | 额外信息 | 注意 Using filesort, Using temporary |
| possible_keys | 可能使用的索引 | 数据库考虑使用的索引列表 |
MySQL EXPLAIN 中 type 字段的详细解释
type 列显示 MySQL 如何查找表中的行,从最优到最差排序如下:
一、性能最好的类型
1. system
- 解释:表只有一行(相当于系统表)
- 出现场景:
- 表中只有一条记录(MyISAM、MEMORY表)
- 衍生表(派生表)只有一行数据
- 示例:
-- 假设表只有一行数据 EXPLAIN SELECT * FROM (SELECT * FROM t1 WHERE id=1) AS derived_table;
2. const
- 解释:通过索引一次就找到了,用于 primary key 或 unique key 的等值查询
- 特点:
- 查询速度非常快
- 只会读取一次
- 返回最多一条记录
- 示例:
EXPLAIN SELECT * FROM users WHERE id = 1; -- id 是主键
二、性能优秀的类型
3. eq_ref
- 解释:使用唯一索引进行关联查询
- 出现场景:
- 关联查询中,被驱动表使用主键或唯一索引进行等值匹配
- 每个索引键值在表中只有一条记录匹配
- 示例:
EXPLAIN SELECT * FROM orders JOIN users ON orders.user_id = users.id; -- users.id 是主键 - 注意:这是除了 system 和 const 之外最好的连接类型
4. ref
- 解释:使用非唯一索引进行等值查询
- 出现场景:
- 使用普通索引(非唯一索引)进行等值查询
- 可能返回多行数据
- 示例:
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com'; -- email 字段有普通索引,但不是唯一索引
5. fulltext
- 解释:使用全文索引
- 出现场景:
- 查询使用了
MATCH() ... AGAINST()语法 - 表中建立了全文索引
- 查询使用了
- 示例:
EXPLAIN SELECT * FROM articles WHERE MATCH(title, content) AGAINST('MySQL tutorial');
三、性能中等的类型
6. ref_or_null
- 解释:类似于 ref,但额外搜索包含 NULL 值的行
- 出现场景:
- 等值查询条件中包含 OR ... IS NULL
- 示例:
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com' OR email IS NULL;
7. index_merge
- 解释:使用了索引合并优化
- 出现场景:
- 查询条件使用多个索引,MySQL 会合并这些索引的结果
- 常见于 OR 条件的查询
- 示例:
EXPLAIN SELECT * FROM users WHERE name = 'John' OR age = 30; -- name 和 age 分别有索引
8. unique_subquery
- 解释:在子查询中使用了唯一索引
- 出现场景:
- 子查询中使用主键或唯一索引
- 通常用于 IN 子查询优化
- 示例:
EXPLAIN SELECT * FROM orders WHERE user_id IN ( SELECT id FROM users WHERE status = 'active' ); -- 如果 users.id 是主键,子查询会使用 unique_subquery
9. index_subquery
- 解释:类似于 unique_subquery,但用于非唯一索引
- 出现场景:
- 子查询中使用普通索引
- 示例:
EXPLAIN SELECT * FROM orders WHERE product_id IN ( SELECT id FROM products WHERE category = 'electronics' ); -- 如果 products.id 是普通索引
四、性能需要优化的类型
10. range
- 解释:使用索引进行范围查询
- 出现场景:
BETWEEN、>、<、>=、<=、IN()、LIKE 'prefix%'等操作- 只检索给定范围的行
- 示例:
EXPLAIN SELECT * FROM users WHERE age BETWEEN 20 AND 30; EXPLAIN SELECT * FROM users WHERE last_name LIKE 'Smith%'; EXPLAIN SELECT * FROM users WHERE id IN (1, 2, 3);
11. index
- 解释:全索引扫描
- 两种场景:
- 覆盖索引:查询的列都包含在索引中,不需要回表
EXPLAIN SELECT id, name FROM users WHERE name LIKE 'A%'; -- 如果 (id, name) 是复合索引 - 索引排序:使用索引进行全表扫描,但按索引顺序
EXPLAIN SELECT * FROM users ORDER BY id; -- 如果 id 是主键
- 覆盖索引:查询的列都包含在索引中,不需要回表
- 特点:比 ALL 快,因为索引通常比表数据小
五、需要避免的性能问题类型
12. ALL
- 解释:全表扫描
- 出现场景:
- 没有使用索引
- 索引失效
- 表数据量很小,全表扫描更快
- 示例:
EXPLAIN SELECT * FROM users WHERE phone = '123456789'; -- phone 字段没有索引
六、性能对比图表
性能从高到低:
system > const > eq_ref > ref > fulltext > ref_or_null >
index_merge > unique_subquery > index_subquery > range > index > ALL
视觉化性能对比:
查询类型 | 性能 | 扫描方式
-----------------|------|--------
system | ⭐⭐⭐⭐⭐ | 单行,常量
const | ⭐⭐⭐⭐⭐ | 主键/唯一索引查找
eq_ref | ⭐⭐⭐⭐ | 唯一索引关联
ref | ⭐⭐⭐ | 非唯一索引查找
range | ⭐⭐ | 索引范围扫描
index | ⭐ | 全索引扫描
ALL | 🚫 | 全表扫描
七、实际优化建议
1. 理想情况:至少达到 range 级别
-- 目标:使 type 达到 range 或更好
EXPLAIN SELECT * FROM logs WHERE created_at > '2023-01-01';
-- 为 created_at 添加索引
CREATE INDEX idx_created_at ON logs(created_at);
2. 避免 ALL 类型
-- 优化前:type = ALL(全表扫描)
EXPLAIN SELECT * FROM products WHERE description LIKE '%discount%';
-- 优化方案1:添加全文索引(对于文本搜索)
CREATE FULLTEXT INDEX idx_description ON products(description);
-- 优化方案2:使用更精确的条件
EXPLAIN SELECT * FROM products
WHERE tag = 'discount' -- 添加 tag 字段并建立索引
AND description LIKE '%discount%';
3. 索引覆盖优化
-- 优化前:可能使用 index 或 range
EXPLAIN SELECT id, name, email FROM users
WHERE email LIKE 'a%';
-- 优化后:创建覆盖索引
CREATE INDEX idx_email_name ON users(email, name);
-- 现在查询只扫描索引,不需要回表
八、常见问题排查
问题1:为什么使用了索引还是慢?
EXPLAIN SELECT * FROM orders
WHERE status = 'shipped' AND created_at > '2023-01-01';
-- type 可能是 range 或 ref,但:
-- 1. 检查选择性:如果 status='shipped' 占 90% 的数据,索引效果差
-- 2. 考虑创建复合索引:CREATE INDEX idx_status_created ON orders(status, created_at)
问题2:为什么没使用我期望的索引?
-- 强制使用索引查看效果
EXPLAIN SELECT * FROM users USE INDEX (idx_email)
WHERE email LIKE 'a%';
-- 对比不使用索引
EXPLAIN SELECT * FROM users IGNORE INDEX (idx_email)
WHERE email LIKE 'a%';
记住:type 只是性能评估的一个维度,还需要结合 rows(扫描行数)、Extra(额外信息)等字段综合判断查询性能。