Mysql:SQL优化常用手段及explain使用(一)

当面对一条慢SQL时,可以按此清单排查:

  1. 能用缓存吗?​ -> 避免查询。

  2. EXPLAIN分析了吗?​ -> 定位问题根源。

  3. 用上索引了吗?​ -> 检查typekey列。

  4. 索引失效了吗?​ -> 检查写法,避免函数、左模糊、类型转换。

  5. 能用覆盖索引吗?​ -> 检查Extra列是否有Using index

  6. Using filesortUsing temporary吗?​ -> 优化ORDER BY/GROUP BY

  7. SELECT *吗?​ -> 改为指定字段。

  8. 分页查询慢吗?​ -> 优化LIMIT

  9. 子查询能变JOIN吗?​ -> 改写SQL。

  10. 单机瓶颈到了吗?​ -> 考虑读写分离、分库分表。

EXPLAIN 输出结果详解

先看一个例子:

EXPLAIN SELECT * FROM orders o 
JOIN customers c ON o.customer_id = c.id 
WHERE o.status = 'shipped' 
ORDER BY o.created_at DESC 
LIMIT 10;

结果:

id

select_type

table

partitions

type

possible_keys

key

key_len

ref

rows

filtered

Extra

1

SIMPLE

o

NULL

ref

idx_status,idx_customer

idx_status

102

const

156

100.00

Using where; Using filesort

1

SIMPLE

c

NULL

eq_ref

PRIMARY

PRIMARY

4

db.o.customer_id

1

100.00

NULL

下面逐列详解每个字段的含义和调优要点:

1. id(执行顺序)

  • 表示 SELECT 的序号

  • id 相同:执行顺序从上到下

  • id 不同:id 值越大,越先执行

  • id 为 NULL:表示是结果集,如 UNION 结果

-- 示例:子查询 id 不同
EXPLAIN 
SELECT * FROM users WHERE id IN (
    SELECT user_id FROM orders WHERE amount > 1000
);
-- 会看到两个不同的 id

2. select_type(查询类型)

表示查询的类型,常见的有:

类型

含义

调优关注点

SIMPLE

简单查询(无子查询、UNION)

最常见的类型

PRIMARY

最外层查询

通常不需要优化

SUBQUERY

子查询中的第一个 SELECT

注意子查询性能

DERIVED

派生表(FROM 中的子查询)

性能杀手!​ 考虑改写为 JOIN

UNION

UNION 中的第二个及以后的 SELECT

注意 UNION 的去重开销

UNION RESULT

UNION 的结果

-

MATERIALIZED

物化子查询(MySQL 5.6+)

子查询结果被物化成临时表

重点关注 DERIVED

-- 差:使用派生表
EXPLAIN SELECT * FROM (
    SELECT * FROM users WHERE status = 1
) AS t WHERE t.age > 20;
-- select_type: DERIVED

-- 好:改写为普通查询
EXPLAIN SELECT * FROM users 
WHERE status = 1 AND age > 20;

3. table(访问的表)

  • 显示正在访问的表名

  • 可能是:表名、别名、<derivedN>(派生表)、<unionM,N>(UNION 结果)

4. type(访问类型)

这是最重要的列!​ 表示 MySQL 如何查找表中的行,从好到坏

类型

说明

性能

调优目标

system

表中只有一行数据

最优

极少见

const

通过主键或唯一索引一次找到

最优

理想情况

eq_ref

主键或唯一索引关联查询

优秀

JOIN 的理想情况

ref

非唯一索引等值查询

良好

常见优化目标

fulltext

全文索引

良好

全文搜索时使用

ref_or_null

ref,但包含 NULL 值

良好

-

index_merge

索引合并

良好

多个单列索引时可能

unique_subquery

IN 子查询使用主键

良好

-

index_subquery

IN 子查询使用非唯一索引

良好

-

range

索引范围扫描

可接受

范围查询的正常表现

index

全索引扫描

较差

考虑优化

ALL

全表扫描

最差

必须优化!

实战判断标准:

-- 1. const/eq_ref(最优)
EXPLAIN SELECT * FROM users WHERE id = 1;
-- type: const

-- 2. ref(良好)
-- 假设在 email 上有普通索引
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
-- type: ref

-- 3. range(可接受)
EXPLAIN SELECT * FROM users WHERE age BETWEEN 20 AND 30;
-- type: range

-- 4. index(较差)
-- 查询的列都在索引中,但需要扫描整个索引
EXPLAIN SELECT id FROM users;  -- id 是主键
-- type: index

-- 5. ALL(最差,必须优化!)
EXPLAIN SELECT * FROM users WHERE phone = '13800138000';
-- 如果 phone 没有索引,type: ALL

调优目标:至少达到 range 级别,避免 index 和 ALL。

5. possible_keys(可能用到的索引)

  • MySQL 分析后可能使用的索引

  • 如果为 NULL,表示没有可用的索引

6. key(实际使用的索引)

  • MySQL 实际决定使用的索引

  • 如果为 NULL,表示没有使用索引,需要优化

  • 可能不在 possible_keys 中,优化器会选择它认为最优的

-- 强制使用索引
EXPLAIN SELECT * FROM users USE INDEX(idx_email) WHERE email LIKE 'a%';

-- 忽略索引
EXPLAIN SELECT * FROM users IGNORE INDEX(idx_email) WHERE email = 'test@example.com';

7. key_len(索引使用长度)

  • 索引中使用的字节数

  • 判断索引使用情况的重要指标

    • 值越大,使用的索引部分越多

    • 可判断是否使用了复合索引的所有部分

-- 示例:复合索引 (name, age, city)
CREATE INDEX idx_multi ON users(name(10), age, city(20));

EXPLAIN SELECT * FROM users WHERE name = '张三';
-- key_len: 31 (10 * 3 + 1) 使用了 name 部分

EXPLAIN SELECT * FROM users WHERE name = '张三' AND age = 25;
-- key_len: 35 (31 + 4) 使用了 name 和 age 部分

计算规则

  • 字符串:字符数 × 字符集字节数 + 2(变长字段)

  • 整数:TINYINT 1, SMALLINT 2, INT 4, BIGINT 8

  • 时间:DATE 3, TIMESTAMP 4, DATETIME 8

  • NULL 标记:允许 NULL 时 +1

8. ref(索引等值匹配的值)

  • 显示索引的哪一列被使用了

  • 常见值:const(常量)、字段名、NULL

EXPLAIN SELECT * FROM orders o 
JOIN users u ON o.user_id = u.id;
-- 对于 users 表:ref: db.o.user_id

9. rows(预估扫描行数)

  • MySQL 预估需要扫描的行数

  • 值越小越好

  • 基于统计信息估算,可能不准确

调优目标:尽可能减少 rows 值

10. filtered(过滤百分比)

  • 表示存储引擎返回的数据在 server 层过滤后,剩下多少百分比

  • 值越大越好,100 表示全部返回

  • 8.0 之前版本没有此列

11. Extra(额外信息)

包含非常重要的执行细节:

含义

调优建议

Using index

覆盖索引,直接从索引获取数据

✅ 优秀!

Using where

在存储引擎后进行了过滤

正常

Using temporary

使用了临时表

⚠️ 考虑优化 GROUP BY/ORDER BY

Using filesort

需要额外排序

⚠️ 考虑添加索引优化 ORDER BY

Using join buffer

使用了连接缓存

可适当增大 join_buffer_size

Impossible WHERE

WHERE 条件永远不成立

检查 WHERE 条件

Select tables optimized away

优化器优化了查询

✅ 优秀

Distinct

优化 DISTINCT

-

Start temporary, End temporary

半连接优化

-

重点关注的问题:

1. Using filesort(文件排序)

-- 没有合适的索引排序
EXPLAIN SELECT * FROM users ORDER BY created_at;
-- Extra: Using filesort

-- 优化:添加索引
ALTER TABLE users ADD INDEX idx_created(created_at);
EXPLAIN SELECT * FROM users ORDER BY created_at;
-- Extra: Using index  (如果只查 created_at)

2. Using temporary(临时表)

-- GROUP BY 无索引
EXPLAIN SELECT city, COUNT(*) FROM users GROUP BY city;
-- Extra: Using temporary; Using filesort

-- 优化:添加索引
ALTER TABLE users ADD INDEX idx_city(city);
-- 或使用覆盖索引
EXPLAIN SELECT city, COUNT(*) FROM users GROUP BY city;
-- Extra: Using index

3. Using index(覆盖索引)

-- 理想情况:查询的列都在索引中
EXPLAIN SELECT id, name FROM users WHERE name LIKE '张%';
-- 假设有索引 (name, id) 或 (id, name)
-- Extra: Using index

EXPLAIN 调优检查清单

 第一优先级:必须优化的

  1. type 是否为 ALL?​ → 添加合适的索引

  2. key 是否为 NULL?​ → 检查索引是否失效

  3. rows 是否过大?​ → 尝试减少扫描行数

  4. 是否有 Using temporary?​ → 优化 GROUP BY/DISTINCT

  5. 是否有 Using filesort?​ → 优化 ORDER BY

第二优先级:应该优化的

  1. type 是否为 index?​ → 考虑使用覆盖索引

  2. Extra 是否有 Using join buffer?​ → 检查 JOIN 条件索引

  3. filtered 是否很低?​ → 索引选择性可能不好

  4. 是否有 DERIVED 类型?​ → 考虑改写子查询

高级优化

  1. 能否使用覆盖索引?​ → 添加包含所有查询列的索引

  2. 索引选择性如何?​ → 高选择性的列放索引前面

  3. 符合最左前缀吗?​ → 调整 WHERE 条件顺序

  4. 是否需要索引下推?​ → MySQL 5.6+ 特性

下一篇接着写一些可供参考的explain案例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

抹茶苹果梨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值