252 lines
7.7 KiB
Markdown
252 lines
7.7 KiB
Markdown
## 解读 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表)
|
||
- 衍生表(派生表)只有一行数据
|
||
- **示例**:
|
||
```sql
|
||
-- 假设表只有一行数据
|
||
EXPLAIN SELECT * FROM (SELECT * FROM t1 WHERE id=1) AS derived_table;
|
||
```
|
||
|
||
### **2. const**
|
||
- **解释**:通过索引一次就找到了,用于 primary key 或 unique key 的等值查询
|
||
- **特点**:
|
||
- 查询速度非常快
|
||
- 只会读取一次
|
||
- 返回最多一条记录
|
||
- **示例**:
|
||
```sql
|
||
EXPLAIN SELECT * FROM users WHERE id = 1;
|
||
-- id 是主键
|
||
```
|
||
|
||
## 二、性能优秀的类型
|
||
|
||
### **3. eq_ref**
|
||
- **解释**:使用唯一索引进行关联查询
|
||
- **出现场景**:
|
||
- 关联查询中,被驱动表使用主键或唯一索引进行等值匹配
|
||
- 每个索引键值在表中只有一条记录匹配
|
||
- **示例**:
|
||
```sql
|
||
EXPLAIN SELECT *
|
||
FROM orders
|
||
JOIN users ON orders.user_id = users.id; -- users.id 是主键
|
||
```
|
||
- **注意**:这是除了 system 和 const 之外最好的连接类型
|
||
|
||
### **4. ref**
|
||
- **解释**:使用非唯一索引进行等值查询
|
||
- **出现场景**:
|
||
- 使用普通索引(非唯一索引)进行等值查询
|
||
- 可能返回多行数据
|
||
- **示例**:
|
||
```sql
|
||
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
|
||
-- email 字段有普通索引,但不是唯一索引
|
||
```
|
||
|
||
### **5. fulltext**
|
||
- **解释**:使用全文索引
|
||
- **出现场景**:
|
||
- 查询使用了 `MATCH() ... AGAINST()` 语法
|
||
- 表中建立了全文索引
|
||
- **示例**:
|
||
```sql
|
||
EXPLAIN SELECT * FROM articles
|
||
WHERE MATCH(title, content) AGAINST('MySQL tutorial');
|
||
```
|
||
|
||
## 三、性能中等的类型
|
||
|
||
### **6. ref_or_null**
|
||
- **解释**:类似于 ref,但额外搜索包含 NULL 值的行
|
||
- **出现场景**:
|
||
- 等值查询条件中包含 OR ... IS NULL
|
||
- **示例**:
|
||
```sql
|
||
EXPLAIN SELECT * FROM users
|
||
WHERE email = 'test@example.com' OR email IS NULL;
|
||
```
|
||
|
||
### **7. index_merge**
|
||
- **解释**:使用了索引合并优化
|
||
- **出现场景**:
|
||
- 查询条件使用多个索引,MySQL 会合并这些索引的结果
|
||
- 常见于 OR 条件的查询
|
||
- **示例**:
|
||
```sql
|
||
EXPLAIN SELECT * FROM users
|
||
WHERE name = 'John' OR age = 30;
|
||
-- name 和 age 分别有索引
|
||
```
|
||
|
||
### **8. unique_subquery**
|
||
- **解释**:在子查询中使用了唯一索引
|
||
- **出现场景**:
|
||
- 子查询中使用主键或唯一索引
|
||
- 通常用于 IN 子查询优化
|
||
- **示例**:
|
||
```sql
|
||
EXPLAIN SELECT * FROM orders
|
||
WHERE user_id IN (
|
||
SELECT id FROM users WHERE status = 'active'
|
||
);
|
||
-- 如果 users.id 是主键,子查询会使用 unique_subquery
|
||
```
|
||
|
||
### **9. index_subquery**
|
||
- **解释**:类似于 unique_subquery,但用于非唯一索引
|
||
- **出现场景**:
|
||
- 子查询中使用普通索引
|
||
- **示例**:
|
||
```sql
|
||
EXPLAIN SELECT * FROM orders
|
||
WHERE product_id IN (
|
||
SELECT id FROM products WHERE category = 'electronics'
|
||
);
|
||
-- 如果 products.id 是普通索引
|
||
```
|
||
|
||
## 四、性能需要优化的类型
|
||
|
||
### **10. range**
|
||
- **解释**:使用索引进行范围查询
|
||
- **出现场景**:
|
||
- `BETWEEN`、`>`、`<`、`>=`、`<=`、`IN()`、`LIKE 'prefix%'` 等操作
|
||
- 只检索给定范围的行
|
||
- **示例**:
|
||
```sql
|
||
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**
|
||
- **解释**:全索引扫描
|
||
- **两种场景**:
|
||
1. **覆盖索引**:查询的列都包含在索引中,不需要回表
|
||
```sql
|
||
EXPLAIN SELECT id, name FROM users WHERE name LIKE 'A%';
|
||
-- 如果 (id, name) 是复合索引
|
||
```
|
||
2. **索引排序**:使用索引进行全表扫描,但按索引顺序
|
||
```sql
|
||
EXPLAIN SELECT * FROM users ORDER BY id;
|
||
-- 如果 id 是主键
|
||
```
|
||
- **特点**:比 ALL 快,因为索引通常比表数据小
|
||
|
||
## 五、需要避免的性能问题类型
|
||
|
||
### **12. ALL**
|
||
- **解释**:全表扫描
|
||
- **出现场景**:
|
||
- 没有使用索引
|
||
- 索引失效
|
||
- 表数据量很小,全表扫描更快
|
||
- **示例**:
|
||
```sql
|
||
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** 级别
|
||
```sql
|
||
-- 目标:使 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 类型**
|
||
```sql
|
||
-- 优化前: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. **索引覆盖优化**
|
||
```sql
|
||
-- 优化前:可能使用 index 或 range
|
||
EXPLAIN SELECT id, name, email FROM users
|
||
WHERE email LIKE 'a%';
|
||
|
||
-- 优化后:创建覆盖索引
|
||
CREATE INDEX idx_email_name ON users(email, name);
|
||
-- 现在查询只扫描索引,不需要回表
|
||
```
|
||
|
||
## 八、常见问题排查
|
||
|
||
### 问题1:为什么使用了索引还是慢?
|
||
```sql
|
||
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:为什么没使用我期望的索引?
|
||
```sql
|
||
-- 强制使用索引查看效果
|
||
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`(额外信息)等字段综合判断查询性能。 |