目录
[一、引言:EXPLAIN------MySQL 性能优化的X光机](#一、引言:EXPLAIN——MySQL 性能优化的X光机)
[二、EXPLAIN 基础入门](#二、EXPLAIN 基础入门)
[2.1 什么是 EXPLAIN](#2.1 什么是 EXPLAIN)
[2.2 基本语法](#2.2 基本语法)
[2.3 输出格式](#2.3 输出格式)
[3.1 id](#3.1 id)
[3.2 select_type](#3.2 select_type)
[3.3 type](#3.3 type)
[3.4 possible_keys 和 key](#3.4 possible_keys 和 key)
[3.5 key_len](#3.5 key_len)
[3.6 ref](#3.6 ref)
[3.7 rows](#3.7 rows)
[3.8 filtered](#3.8 filtered)
[四、Extra 字段深度解析](#四、Extra 字段深度解析)
[4.1 Using index](#4.1 Using index)
[4.2 Using where](#4.2 Using where)
[4.3 Using temporary](#4.3 Using temporary)
[4.4 Using filesort](#4.4 Using filesort)
[4.5 Using join buffer (Block Nested Loop)](#4.5 Using join buffer (Block Nested Loop))
[4.6 Using index condition](#4.6 Using index condition)
[4.7 Using index for group-by](#4.7 Using index for group-by)
[4.8 Select tables optimized away](#4.8 Select tables optimized away)
[4.9 Range checked for each record](#4.9 Range checked for each record)
[4.10 Using union](#4.10 Using union)
[5.1 全表扫描优化](#5.1 全表扫描优化)
[5.2 分页查询优化](#5.2 分页查询优化)
[5.3 索引失效优化](#5.3 索引失效优化)
[5.4 子查询优化](#5.4 子查询优化)
[5.5 排序分组优化](#5.5 排序分组优化)
[六、高级 SQL 优化技巧](#六、高级 SQL 优化技巧)
[6.1 索引设计黄金法则](#6.1 索引设计黄金法则)
[6.2 避免索引失效的常见误区](#6.2 避免索引失效的常见误区)
[6.3 数据库配置优化](#6.3 数据库配置优化)
[7.1 核心总结](#7.1 核心总结)
[7.2 性能优化流程](#7.2 性能优化流程)
[7.3 持续优化建议](#7.3 持续优化建议)
一、引言:EXPLAIN------MySQL 性能优化的X光机
在互联网系统中,数据库性能往往是决定系统吞吐量和响应时间的关键因素。而在 MySQL 性能优化领域,EXPLAIN 命令无疑是最强大的工具之一。它就像是给 SQL 语句做了一次 CT 扫描,能够将 MySQL 执行 SQL 时的内部机制完全暴露出来,包括索引使用情况、表连接顺序、数据访问方式等核心信息。
然而,大多数开发者对 EXPLAIN 的理解仅仅停留在"看是否走了索引"这个层面,很少能够深入理解每个字段背后的含义,更不用说利用这些信息进行系统的 SQL 优化。本文将带你全面深入地了解 EXPLAIN 的所有字段,特别是对 Extra 字段进行详细解析,并结合大量实战案例,教你如何通过 EXPLAIN 快速定位并解决 SQL 性能问题。
二、EXPLAIN 基础入门
2.1 什么是 EXPLAIN
EXPLAIN 是 MySQL 提供的一个强大工具,用于分析 SQL 查询语句的执行计划。通过在 SELECT、DELETE、INSERT、UPDATE 等语句前添加 EXPLAIN 关键字,MySQL 会返回该 SQL 语句的执行计划信息,而不是实际执行该语句。
执行计划中包含了 MySQL 优化器对 SQL 语句的理解和处理方式,包括:
-
MySQL 如何查找表中的行
-
使用了哪些索引
-
表之间的连接顺序和连接类型
-
扫描的行数估计
-
需要进行的额外操作(如排序、临时表等)
2.2 基本语法
sql
EXPLAIN SELECT * FROM users WHERE age > 18;
-- 或者使用 DESCRIBE(简写为 DESC)
DESCRIBE SELECT * FROM users WHERE age > 18;
2.3 输出格式
EXPLAIN 的输出结果是一个表格,包含以下字段:
| 字段名 | 含义 | 重要程度 |
|---|---|---|
| id | SELECT 查询的序列号 | ★★★ |
| select_type | 查询类型 | ★★★★ |
| table | 访问的表名或别名 | ★★★ |
| partitions | 匹配的分区 | ★ |
| type | 数据访问类型(关键性能指标) | ★★★★★ |
| possible_keys | 可能使用的索引 | ★★★ |
| key | 实际使用的索引 | ★★★★★ |
| key_len | 使用的索引字节数 | ★★★ |
| ref | 与索引比较的列或常量 | ★★★ |
| rows | 预估扫描行数 | ★★★★ |
| filtered | 存储引擎层过滤后剩余数据的百分比 | ★★★ |
| Extra | 额外执行信息(关键优化提示) | ★★★★★ |
三、核心字段详解
3.1 id
含义 :SELECT 查询的序列号,标识查询中 SELECT 子句的执行顺序。
执行顺序规则:
-
id 相同:按从上到下的顺序执行
-
id 不同:id 值越大,执行优先级越高(如子查询)
-
id 为 NULL:最后执行,表示结果集合并操作(如
UNION的结果)
示例:
sql
EXPLAIN SELECT * FROM (SELECT * FROM users) AS t_derived JOIN orders o ON t_derived.id = o.user_id;
执行顺序解析:
-
id=1:主查询,执行连接操作
-
id=2:子查询,先执行子查询生成派生表
3.2 select_type
含义 :表示 SELECT 查询的类型,反映查询的复杂程度。
常见值及其含义:
| select_type | 含义 |
|---|---|
| SIMPLE | 简单查询,不包含子查询或 UNION |
| PRIMARY | 复杂查询中最外层的查询 |
| SUBQUERY | 在 SELECT 或 WHERE 列表中的子查询(非关联子查询) |
| DEPENDENT SUBQUERY | 依赖外层查询的子查询(关联子查询,性能较差) |
| DERIVED | 在 FROM 列表中包含的子查询,MySQL 会将结果放在一张临时表中(派生表) |
| UNION | UNION 中的第二个或后续查询 |
| UNION RESULT | UNION 的结果集 |
优化建议:
-
避免使用
DEPENDENT SUBQUERY,因为这种子查询需要为外层查询的每一行执行一次,性能较差 -
尽量将子查询改写为
JOIN操作
3.3 type
含义 :表示 MySQL 查找表中数据的方式,是 EXPLAIN 结果中最关键的列之一,直接反映查询的性能。
性能排序(从优到劣) :
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
常见值详解:
-
system
-
表示表只有一行记录(系统表),这是
const的特例 -
性能最优,但实际业务中很少出现
-
-
const
-
通过主键或唯一索引一次就能找到匹配的行
-
适用于将主键置于
WHERE条件中的查询
sqlEXPLAIN SELECT * FROM users WHERE id = 1; -
-
eq_ref
- 唯一索引关联查询,对于驱动表的每一行,被驱动表最多返回一条匹配记录
- 适用于
JOIN查询且关联字段为主键或唯一索引的情况
sqlEXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_id; -
ref
-
非唯一索引查找,返回所有匹配某个单独值的行
-
适用于普通索引查询或关联查询的非主键/非唯一索引字段
sqlEXPLAIN SELECT * FROM users WHERE age = 25; -- age 字段有普通索引 -
-
range
- 索引范围扫描,常用于
BETWEEN、>、<、IN、OR等操作
sqlEXPLAIN SELECT * FROM users WHERE id > 100; - 索引范围扫描,常用于
-
index
-
全索引扫描,遍历整个索引树,通常比全表扫描快
-
适用于只需要读取索引字段的查询(覆盖索引)
sqlEXPLAIN SELECT id FROM users; -- id 是主键 -
-
ALL
-
全表扫描,性能最差,需要优先优化
-
表示 MySQL 需要遍历整张表来找到匹配的行
-
优化建议:
-
一般需要保证查询至少达到
range级别,最好能达到ref或更高 -
绝对避免出现
ALL类型的查询,这意味着没有使用任何索引,进行了全表扫描
3.4 possible_keys 和 key
possible_keys :表示 MySQL 优化器认为可能适用的索引,但并不一定在查询时实际使用。如果该字段为 NULL,表示没有适用的索引。
key :表示 MySQL 实际选择使用的索引。如果该字段为 NULL,表示没有使用索引,MySQL 可能执行了全表扫描。
优化建议:
-
当
possible_keys有值但key为NULL时,说明 MySQL 认为索引的选择性较差,没有使用索引。这时需要检查索引是否合理,或者是否有必要调整 SQL 语句。 -
可以通过
FORCE INDEX强制使用某个索引,或通过IGNORE INDEX忽略某个索引。
sql
-- 强制使用 idx_age 索引
EXPLAIN SELECT * FROM users FORCE INDEX(idx_age) WHERE age = 25;
3.5 key_len
含义:表示 MySQL 在索引中使用的字节数,可以通过该值计算出具体使用了索引中的哪些列。
计算规则:
-
整数类型:
-
TINYINT:1 字节 -
SMALLINT:2 字节 -
INT:4 字节 -
BIGINT:8 字节
-
-
字符串类型:
-
VARCHAR(n):根据字符集不同而不同,例如utf8mb4每个字符占 4 字节,再加上 2 字节的长度标志 -
CHAR(n):固定长度,utf8mb4每个字符占 4 字节
-
-
允许
NULL的字段需要额外加 1 字节的NULL标志
示例:
sql
ALTER TABLE users ADD INDEX idx_name_age(name, age);
EXPLAIN SELECT * FROM users WHERE name = '张三' AND age = 25;
如果 name 是 VARCHAR(10)(utf8mb4),age 是 INT,则 key_len 的计算为:
10 * 4 + 2 + 4 + 1 = 47
-
10 * 4:name字段占用的字节数 -
+ 2:VARCHAR类型的长度标志 -
+ 4:age字段占用的字节数 -
+ 1:允许NULL的标志(如果字段不允许NULL则不加)
3.6 ref
含义:表示与索引比较的列或常量,显示哪些列或常量被用于查找索引列上的值。
常见值:
-
const:表示使用常量值进行匹配 -
列名:表示使用其他表的列进行比较
-
func:表示使用函数结果进行匹配
示例:
sql
-- ref: const
EXPLAIN SELECT * FROM users WHERE id = 1;
-- ref: db1.users.id
EXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_id;
3.7 rows
含义 :表示 MySQL 预估的为了找到所需行需要读取的行数。对于 InnoDB 表来说,这只是一个估计值,可能与实际情况有一定偏差。
优化建议:
-
该值越小越好,数值越大表示查询的性能越差
-
如果
rows与实际行数偏差过大,可以考虑执行ANALYZE TABLE更新表统计信息
3.8 filtered
含义 :表示存储引擎层过滤后剩余数据的百分比(0~100)。例如,如果 rows 是 1000,filtered 是 10.00(即 10%),表示存储引擎返回了 1000 行,但在服务层过滤后只剩下了 100 行。
优化建议:
-
filtered值越低,表示服务层需要过滤的数据越多,性能越差 -
如果
filtered值较低,可以考虑优化WHERE条件,尽量让更多的过滤在存储引擎层完成(通过索引下推等特性)
四、Extra 字段深度解析
Extra 字段是 EXPLAIN 结果中最重要的字段之一,包含了大量关于查询执行的额外信息,是发现查询性能问题和优化的关键。
4.1 Using index
含义:表示 MySQL 使用了覆盖索引(Covering Index),直接从索引中返回所需列,不需要回表访问数据行。
优化建议:
-
这是一种高效的访问方式,说明查询语句写得比较好,索引设计合理
-
可以进一步扩展索引以覆盖更多常用查询列,减少回表操作
4.2 Using where
含义 :表示 MySQL 在存储引擎检索行后需要通过 WHERE 条件进一步过滤数据。
常见场景:
-
查询没有使用全部索引列,需要额外过滤
-
索引范围扫描后,还需在存储引擎层做行级过滤
优化建议:
-
尽量让过滤条件在索引层完成,减少回表操作
-
可以使用索引条件下推(ICP)特性,让更多过滤条件在索引层完成
4.3 Using temporary
含义 :表示 MySQL 需要创建临时表来保存中间结果。这通常出现在含有 GROUP BY、DISTINCT、ORDER BY 等子句的复杂查询中,尤其是当这些子句中的列没有合适的索引时。
常见场景:
-
GROUP BY且分组字段没有合适的索引 -
ORDER BY且排序字段没有合适的索引 -
DISTINCT操作 -
复杂子查询或多表
JOIN
优化建议:
-
为
GROUP BY或ORDER BY子句中的列添加适当的索引 -
检查是否可以简化查询,避免使用临时表
-
适当增加
tmp_table_size和max_heap_table_size参数值,让临时表尽可能在内存中处理
4.4 Using filesort
含义 :表示 MySQL 需要额外的一次传递来进行排序。当 ORDER BY 子句中的列不是索引的一部分,或者索引的顺序与 ORDER BY 要求的顺序不一致时,就会出现这种情况。
常见场景:
-
ORDER BY字段未使用索引 -
排序字段与
WHERE条件列不在同一个索引 -
排序方向不一致(
ASC/DESC混合)
优化建议:
-
为
ORDER BY子句中的列创建合适的索引 -
确保索引的列顺序与
ORDER BY子句的顺序一致,且方向相同 -
必要时使用"反向列"存储倒序值,保证统一方向
4.5 Using join buffer (Block Nested Loop)
含义 :表示 MySQL 在做 JOIN 时使用了连接缓存区,因为被驱动表没有可用索引。
常见场景:
-
被驱动表(
JOIN后的表)关联字段无索引 -
多表关联条件不走索引
优化建议:
-
在
JOIN的ON条件字段上建立索引 -
调整表顺序,让小表做驱动表
-
考虑分步查询减少
JOIN
4.6 Using index condition
含义:表示 MySQL 使用了索引条件下推(Index Condition Pushdown, ICP)优化。MySQL 会在存储引擎层使用索引中的信息来预先过滤记录,减少回表操作。
优化建议:
-
这是一种优化手段,说明 MySQL 正在使用 ICP 特性
-
确保索引包含所有可能的过滤条件字段,进一步减少回表操作
4.7 Using index for group-by
含义:表示 MySQL 利用索引顺序直接完成分组,不需要额外排序。
常见场景:
GROUP BY的列正好是索引的前缀
优化建议:
- 按
GROUP BY列的顺序建立索引,充分利用索引的有序性
4.8 Select tables optimized away
含义:表示 MySQL 在优化阶段就直接计算了结果,不需要真正访问表。
常见场景:
-
查询聚合函数(如
MIN/MAX/COUNT)且有索引 -
简单的常量查询
优化建议:
- 这种情况不需要额外优化,说明 MySQL 已经对查询进行了最大限度的优化
4.9 Range checked for each record
含义 :表示 MySQL 找不到好的索引,需要对驱动表的每一行检查被驱动表中行的范围。这通常意味着 SQL 语句的 JOIN 部分效率低下。
优化建议:
-
在
JOIN条件相关的列上创建索引 -
调整查询结构,拆分为多个简单查询
-
执行
ANALYZE TABLE更新表统计信息
4.10 Using union
含义:表示 MySQL 使用了索引合并优化方法,将多个单列索引的结果集进行合并(取并集)。
常见场景:
WHERE条件中是OR连接的不同索引列
优化建议
-
优先使用复合索引替代索引合并,因为复合索引的性能通常更好
-
如果无法使用复合索引,可以考虑将
OR条件拆分为多个UNION查询
五、实战案例分析
5.1 全表扫描优化
问题场景:某电商系统的订单查询页面响应缓慢,查询 SQL 如下:
sql
EXPLAIN SELECT * FROM orders WHERE status = 'completed';
执行计划:
TypeScript
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
---|-------------|--------|------|---------------|------|---------|------|-------|-------
1 | SIMPLE | orders | ALL | NULL | NULL | NULL | NULL | 100000| Using where
问题分析:
-
type为ALL,表示进行了全表扫描 -
possible_keys为NULL,说明没有可用的索引 -
rows为 100000,需要扫描 10 万行数据
优化方案:
为 status 字段添加索引:
sql
ALTER TABLE orders ADD INDEX idx_status(status);
优化后执行计划:
TypeScript
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
---|-------------|--------|------|---------------|-----------|---------|------|------|-------
1 | SIMPLE | orders | ref | idx_status | idx_status| 62 | const| 1000 | Using where
优化效果:
-
type从ALL提升为ref -
rows从 100000 下降为 1000 -
查询时间从 1.2 秒下降到 0.01 秒
5.2 分页查询优化
问题场景:电商商品列表分页查询,当页数较多时,查询速度变慢:
sql
EXPLAIN SELECT * FROM goods WHERE category_id = 10 LIMIT 10000, 10;
执行计划:
TypeScript
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
---|-------------|-------|------|---------------|------------|---------|------|-------|-------
1 | SIMPLE | goods | ALL | NULL | NULL | NULL | NULL | 100000| Using where; Using filesort
问题分析:
-
LIMIT 10000, 10表示 MySQL 需要扫描前 10010 条记录,然后丢弃前 10000 条,只返回最后 10 条 -
这种深分页方式的性能会随着页数的增加急剧下降
优化方案:
方案一:使用主键过滤(适用于连续主键)
sql
SELECT * FROM goods
WHERE category_id = 10 AND goods_id > 10000
LIMIT 10;
方案二:延迟关联(适用于非连续主键)
sql
SELECT g.goods_id, g.name, g.price
FROM (SELECT goods_id FROM goods WHERE category_id = 10 LIMIT 10000, 10) AS t
JOIN goods g ON t.goods_id = g.goods_id;
优化效果:
查询时间从 500ms 下降到 10ms,性能提升 50 倍以上。
5.3 索引失效优化
问题场景 :用户登录查询,表中已经为 phone 字段创建了索引,但查询速度依然很慢:
sql
EXPLAIN SELECT user_id, username FROM user WHERE phone = 13800138000;
执行计划:
TypeScript
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
---|-------------|-------|------|---------------|------|---------|------|-------|-------
1 | SIMPLE | user | ALL | NULL | NULL | NULL | NULL | 1000000| Using where
问题分析:
-
type为ALL,表示进行了全表扫描 -
possible_keys为NULL,说明索引失效
原因排查:
查看表结构发现 phone 字段是 VARCHAR 类型,但查询时使用了数字类型进行比较,导致隐式类型转换,索引失效。
优化方案:
sql
-- 确保查询条件与字段类型一致
EXPLAIN SELECT user_id, username FROM user WHERE phone = '13800138000';
优化后执行计划:
TypeScript
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
---|-------------|-------|------|---------------|----------------|---------|------|------|-------
1 | SIMPLE | user | const| idx_user_phone| idx_user_phone | 34 | const| 1 |
优化效果:
-
type变为const,表示通过主键或唯一索引一次就找到 -
查询时间从 0.8 秒下降到 0.001 秒,性能提升 800 倍
5.4 子查询优化
问题场景:查询所有有订单的用户信息:
sql
EXPLAIN SELECT * FROM products WHERE id IN (SELECT product_id FROM orders);
执行计划:
TypeScript
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
---|-------------------|-----------|------|---------------|------|---------|------|--------|-------
1 | PRIMARY | products | ALL | NULL | NULL | NULL | NULL | 10000 | Using where
2 | DEPENDENT SUBQUERY| orders | ref | idx_product_id| idx_product_id| 4 | func | 10000 |
问题分析:
-
select_type为DEPENDENT SUBQUERY,表示这是一个关联子查询 -
这种子查询需要为外层查询的每一行执行一次,性能较差
优化方案:
将子查询改写为 JOIN 操作:
EXPLAIN SELECT DISTINCT p.* FROM products p JOIN orders o ON p.id = o.product_id;
优化后执行计划:
TypeScript
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
---|-------------|--------|------|---------------|----------------|---------|--------------|-------|-------
1 | SIMPLE | o | ALL | NULL | NULL | NULL | NULL | 100000| Using temporary
2 | SIMPLE | p | eq_ref| PRIMARY | PRIMARY | 4 | db1.o.product_id| 1 |
优化效果:
-
避免了关联子查询的性能问题
-
查询时间从 2.3 秒下降到 0.2 秒,性能提升 10 倍以上
5.5 排序分组优化
问题场景:查询每个部门薪资最高的员工:
sql
EXPLAIN SELECT * FROM employees e1
WHERE salary = (SELECT MAX(salary) FROM employees e2 WHERE e2.department_id = e1.department_id);
执行计划:
TypeScript
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
---|-------------------|-----------|------|---------------|------|---------|------|-------|-------
1 | PRIMARY | e1 | ALL | NULL | NULL | NULL | NULL | 10000 | Using where
2 | DEPENDENT SUBQUERY| e2 | ref | idx_department| idx_department| 4 | func | 1000 | Using index condition
问题分析:
-
使用了关联子查询,性能较差
-
需要对每个部门执行一次子查询
优化方案:
使用窗口函数(MySQL 8.0+)进行优化:
sql
EXPLAIN WITH ranked_employees AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) as rn
FROM employees
)
SELECT * FROM ranked_employees WHERE rn = 1;
优化效果:
-
只需要扫描一次表即可完成查询
-
查询时间从 3.5 秒下降到 0.3 秒,性能提升 10 倍以上
六、高级 SQL 优化技巧
6.1 索引设计黄金法则
1. 最左前缀匹配:
复合索引的使用遵循最左前缀原则,即 INDEX(a,b,c) 可以支持 a、a,b、a,b,c 的查询,但不能支持 b、c、b,c 的查询。
2. 选择性优先:
选择性高的列适合作为索引的前缀,区分度高的列(如用户 ID、订单号)适合建索引,而区分度低的列(如性别、状态)不适合单独建索引
3. 覆盖索引:
如果查询的列完全包含在索引中,MySQL 可以直接使用索引而无需回表,这种索引称为覆盖索引。
6.2 避免索引失效的常见误区
1. 不要在索引列上使用函数或计算:
sql
-- 错误写法(索引失效)
SELECT * FROM users WHERE YEAR(create_time) = 2023;
-- 正确写法(索引有效)
SELECT * FROM users WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31';
2. 避免使用 != 或 <> 操作符:
这些操作符会导致索引失效,尽量使用范围查询替代。
3. 注意隐式类型转换:
查询条件的类型要与字段类型一致,否则会导致索引失效。
4. 避免使用 OR 连接非索引字段:
如果 OR 连接的字段中有一个没有索引,则会导致整个查询的索引失效。
5. LIKE 以通配符开头会导致索引失效:
sql
-- 错误写法(索引失效)
SELECT * FROM users WHERE username LIKE '%陈%';
-- 正确写法(索引有效)
SELECT * FROM users WHERE username LIKE '陈%';
6.3 数据库配置优化
1. 调整缓冲区大小:
-
innodb_buffer_pool_size:InnoDB的缓冲池大小,建议设置为物理内存的 50~70% -
sort_buffer_size:排序缓冲区大小 -
join_buffer_size:连接缓冲区大小
2. 开启慢查询日志:
sql
-- 开启慢查询日志
slow_query_log = 1
-- 设置慢查询阈值(秒)
long_query_time = 2
-- 记录没有使用索引的查询
log_queries_not_using_indexes = 1
3. 其他优化参数:
-
tmp_table_size和max_heap_table_size:设置临时表的最大内存大小 -
innodb_log_file_size:InnoDB的日志文件大小 -
query_cache_size:查询缓存大小(MySQL 8.0+ 已移除)
七、总结与建议
7.1 核心总结
-
type字段是关键 :优先关注type字段,确保查询至少达到range级别,最好能达到ref或更高 -
索引是优化的核心:合理的索引设计是提高查询性能的关键,要遵循最左前缀原则,选择区分度高的列作为索引
-
Extra字段是优化的重点 :特别关注Using filesort和Using temporary,这两个是性能杀手,需要尽快优化 -
避免全表扫描 :绝对避免出现
type=ALL的查询,这意味着没有使用任何索引,进行了全表扫描
7.2 性能优化流程
-
捕获慢 SQL:通过慢查询日志或性能监控工具发现慢查询
-
EXPLAIN分析 :使用EXPLAIN查看执行计划,定位性能瓶颈 -
索引优化:添加缺失的索引,调整索引顺序,使用覆盖索引
-
SQL 重写 :将子查询改写为
JOIN,优化WHERE条件,避免索引失效 -
配置调整:根据需要调整数据库配置参数
-
验证效果 :使用
EXPLAIN或实际执行时间验证优化效果
7.3 持续优化建议
-
定期分析慢查询日志:每周或每月分析慢查询日志,发现潜在的性能问题
-
更新统计信息 :定期执行
ANALYZE TABLE更新表统计信息,帮助 MySQL 优化器做出更准确的选择 -
监控系统性能:使用性能监控工具(如 Prometheus + Grafana)实时监控数据库性能
-
持续学习:关注 MySQL 官方文档和性能优化的最新技术,不断提升自己的优化能力
通过本文的学习,相信你已经掌握了 EXPLAIN 的使用方法和 SQL 优化的核心技巧。在实际工作中,要多使用 EXPLAIN 分析查询计划,不断积累优化经验,才能真正成为 SQL 优化的高手。
最后,记住一个核心原则:能走索引,就不全表扫描;能覆盖索引,就不回表;能浅分页,就不深分页。
祝你的 SQL 优化之路一帆风顺!