MySQL EXPLAIN 深度解析与 SQL 优化实战

目录

[一、引言: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 子句的执行顺序。

执行顺序规则:

  1. id 相同:按从上到下的顺序执行

  2. id 不同:id 值越大,执行优先级越高(如子查询)

  3. 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

常见值详解:

  1. system

    • 表示表只有一行记录(系统表),这是 const 的特例

    • 性能最优,但实际业务中很少出现

  2. const

    • 通过主键或唯一索引一次就能找到匹配的行

    • 适用于将主键置于 WHERE 条件中的查询

    sql 复制代码
    EXPLAIN SELECT * FROM users WHERE id = 1;
  3. eq_ref

    • 唯一索引关联查询,对于驱动表的每一行,被驱动表最多返回一条匹配记录
    • 适用于 JOIN 查询且关联字段为主键或唯一索引的情况
    sql 复制代码
    EXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_id;
  4. ref

    • 非唯一索引查找,返回所有匹配某个单独值的行

    • 适用于普通索引查询或关联查询的非主键/非唯一索引字段

    sql 复制代码
    EXPLAIN SELECT * FROM users WHERE age = 25; -- age 字段有普通索引
  5. range

    • 索引范围扫描,常用于 BETWEEN、>、<、IN、OR 等操作
    sql 复制代码
    EXPLAIN SELECT * FROM users WHERE id > 100;
  6. index

    • 全索引扫描,遍历整个索引树,通常比全表扫描快

    • 适用于只需要读取索引字段的查询(覆盖索引)

    sql 复制代码
    EXPLAIN SELECT id FROM users; -- id 是主键
  7. 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 性能优化流程

  1. 捕获慢 SQL:通过慢查询日志或性能监控工具发现慢查询

  2. EXPLAIN 分析 :使用 EXPLAIN 查看执行计划,定位性能瓶颈

  3. 索引优化:添加缺失的索引,调整索引顺序,使用覆盖索引

  4. SQL 重写 :将子查询改写为 JOIN,优化 WHERE 条件,避免索引失效

  5. 配置调整:根据需要调整数据库配置参数

  6. 验证效果 :使用 EXPLAIN 或实际执行时间验证优化效果

7.3 持续优化建议

  • 定期分析慢查询日志:每周或每月分析慢查询日志,发现潜在的性能问题

  • 更新统计信息 :定期执行 ANALYZE TABLE 更新表统计信息,帮助 MySQL 优化器做出更准确的选择

  • 监控系统性能:使用性能监控工具(如 Prometheus + Grafana)实时监控数据库性能

  • 持续学习:关注 MySQL 官方文档和性能优化的最新技术,不断提升自己的优化能力

通过本文的学习,相信你已经掌握了 EXPLAIN 的使用方法和 SQL 优化的核心技巧。在实际工作中,要多使用 EXPLAIN 分析查询计划,不断积累优化经验,才能真正成为 SQL 优化的高手。

最后,记住一个核心原则:能走索引,就不全表扫描;能覆盖索引,就不回表;能浅分页,就不深分页。

祝你的 SQL 优化之路一帆风顺!

相关推荐
许彰午6 小时前
01-Oracle数据库简介与版本选择
数据库·oracle
菜鸟~noob2336 小时前
【电子战】第三季预告—波武器与电子系统强力毁伤
开发语言·数据库·matlab·电子战
hasty8 小时前
policy.xml 在,不代表策略生效:ImageMagick 补丁给安全配置验收的提醒
xml·mysql·安全
Seraphina368 小时前
SQL注入(二)盲注
网络·数据库·经验分享·笔记·sql
盟接之桥9 小时前
AI助手:你的7×24小时智能管家——让异常预警无处不在
大数据·网络·数据库·人工智能·制造·ai编程
waoooqwe9 小时前
问卷样本真实吗
大数据·数据库·算法·数据分析
可乐ea10 小时前
Agent 评测换判据:用终态数据库状态判定任务是否完成
数据库·ui·oracle·mcp工具·agent评测·终态断言·智能体可靠性
l1t10 小时前
DeepSeek总结的一个不含数据的 DuckDB 数据库
服务器·数据库·duckdb
PaperData11 小时前
2007-2020年税收调查面板数据
数据库
程序员清风11 小时前
CSV、Excel 与数据库数据读取实践
数据库·oracle·excel