EXPLAIN:你的SQL性能优化透视镜

目录

一、EXPLAIN是什么?

二、如何使用EXPLAIN?

三、EXPLAIN输出解读(核心字段)

四、EXPLAIN的实际应用场景

五、实战优化案例

[六、使用 EXPLAIN 的注意事项与小贴士](#六、使用 EXPLAIN 的注意事项与小贴士)

七、总结


一、EXPLAIN是什么?

简单来说,EXPLAIN 是一条 SQL 命令(或命令前缀),你将它加在你的 SELECT, INSERT, UPDATE, DELETE, REPLACE 语句之前。数据库不会真正执行你的操作语句,而是返回该语句的"执行计划"

执行计划是什么? 它是数据库优化器(Optimizer)基于当前数据库统计信息(如表大小、索引分布等),为你的 SQL 语句制定的一套最优(或接近最优)的执行方案。它详细说明了:

  1. 如何访问表(全表扫描?走哪个索引?)

  2. 如何连接多个表(使用哪种 JOIN 算法?嵌套循环?哈希连接?排序合并?)

  3. 执行步骤的顺序(先做哪一步,后做哪一步)

  4. 每个步骤的预估成本(需要读取多少行?处理多少数据?)

  5. 是否使用了临时表或文件排序(通常影响性能的关键点)

二、如何使用EXPLAIN?

语法极其简单

sql 复制代码
EXPLAIN SELECT * FROM users WHERE country = 'USA' AND age > 30;

三、EXPLAIN输出解读(核心字段)

执行 EXPLAIN 后,你会得到一个表格(或 JSON/TREE 结构),包含以下关键列:

  1. **id:**查询中 SELECT 子句的执行顺序标识符。相同 id 按 select_type 顺序执行,id 越大优先级越高,NULL 表示是结果行。

  2. **select_type:**查询类型。常见值:

  • **SIMPLE:**简单查询(无子查询或 UNION)。

  • **PRIMARY:**最外层查询。

  • **SUBQUERY:**子查询中的第一个 SELECT。

  • **DERIVED:**派生表(FROM 子句中的子查询结果)。

  • **UNION:**UNION 中第二个及以后的 SELECT。

  • **UNION RESULT:**UNION 的结果。

  1. **table:**当前步骤访问的表名(或别名,或如 <derivedN>, <unionM,N>)。

  2. **partitions:**匹配的分区(如果表分区了)。

  3. **type (非常关键!):**访问类型,即如何查找行。性能从最优到最差排序:

  • 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. **possible_keys:**查询可能使用到的索引列表。

  2. **key:**查询实际决定使用的索引。NULL 表示未使用索引。

  3. **key_len:**使用的索引的长度(字节数)。可判断是否充分利用了复合索引。

  4. **ref:**显示索引的哪一列或常量被用来与 key 一起从表中筛选行。

  5. **rows (重要!):**MySQL 优化器预估需要扫描的行数(不是精确值!)。这个值越小越好,是衡量查询效率的关键指标。

  6. **filtered:**表示存储引擎返回的数据在 MySQL 服务器层进行 WHERE 条件过滤后,剩余行数的百分比预估。100 表示没有在服务器层过滤。

  7. **Extra (包含重要信息!):**额外信息,常揭示性能问题:

  • **Using index:**使用了覆盖索引(查询所需列都在索引中,无需回表),好!

  • **Using where:**服务器层在存储引擎返回行后进行了过滤。

  • **Using temporary:**使用了临时表来处理结果(排序、GROUP BY、DISTINCT 等可能导致),需警惕!

  • **Using filesort:**使用了文件排序(在内存或磁盘排序),需警惕! (尤其对大结果集)

  • **Using join buffer (Block Nested Loop):**使用了连接缓冲区(通常发生在没有合适索引的 JOIN 时)。

  • **Select tables optimized away:**优化器确定只需访问索引,无需访问表(如 MIN(key_col))。

四、EXPLAIN的实际应用场景

  1. 诊断慢查询:

当发现某个查询执行缓慢时,第一时间 EXPLAIN 它!重点看:

  • type 是否为 ALL 或 index?

  • key 是否为 NULL (没走索引)?

  • rows 预估是否巨大?

  • Extra 是否有 Using temporary 或 Using filesort?

  1. 验证索引是否有效:
  • 你创建了一个索引,但 EXPLAIN 显示 key 还是 NULL?可能索引未被选中(如查询条件写法不匹配索引最左前缀、数据类型不匹配、函数操作列、OR 条件不当等)。

  • 检查 possible_keys 是否包含了你期望的索引?key 是否选择了它?

  1. 优化 JOIN 查询:
  • 查看每个表的访问类型 (type)。

  • 查看 rows 预估,驱动表(通常是 rows 小的表)选择是否合理?

  • 检查连接条件 (ON / USING) 上是否有合适的索引 (ref 列)。

  • 是否出现了 Using join buffer?可能需要优化索引或查询。

  1. 优化子查询:
  • 查看子查询的类型 (select_type) 和访问方式 (type)。

  • 考虑是否可重写为 JOIN(有时性能更好)。

  1. **理解复杂查询的执行流程:**通过 id 和 select_type 理清执行顺序和逻辑。

五、实战优化案例

**场景:**查询 orders 表中 2023 年来自 'London' 客户的订单,并按日期排序。

原始查询 (可能慢):

sql 复制代码
SELECT o.*, c.name
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE c.city = 'London' AND o.order_date BETWEEN '2023-01-01' AND '2023-12-31'
ORDER BY o.order_date DESC;

EXPLAIN 结果 (可能的问题):

  • customers 表 type = ALL (全表扫描找 'London' 客户)

  • orders 表 type = ref (用 customer_id 索引),但 rows 可能很多。

  • Extra 可能包含 Using filesort (在 orders 表很大时排序代价高)

优化步骤:

  1. **为 customers(city) 添加索引:**避免全表扫描。

  2. 为 orders(order_date) 添加索引? 可能不够,因为 WHERE 条件主要是 customer_id 和日期范围。考虑复合索引 (customer_id, order_date)

  • 先通过 customer_id 快速定位到该客户的订单。

  • 在该客户的订单中,索引已按 order_date 排序,可以高效进行范围查找 (BETWEEN) 并且 按 order_date 排序 (ORDER BY order_date DESC),可能避免 filesort。如果查询只选择索引包含的列,还能实现覆盖索引 (Using index)。

  1. 修改后 EXPLAIN 验证:
  • customers 表 type 变为 ref (使用 city 索引)。

  • orders 表 type 变为 ref 或 range (使用 (customer_id, order_date) 索引)。

  • Extra 中的 Using filesort 消失 (Using index 可能代替)。

六、使用 EXPLAIN 的注意事项与小贴士

  1. **预估而非精确:**EXPLAIN 输出(尤其是 rows)是基于统计信息的预估值,可能与实际执行有差异。EXPLAIN ANALYZE (PG, MySQL 8.0+) 会真正执行查询并提供实际耗时和行数,但生产环境慎用(尤其是写操作)!

  2. **关注 type 和 rows:**这两项通常是判断查询效率最直观的指标。

  3. **警惕 Using temporary 和 Using filesort:**特别是在处理大数据集时,它们往往是性能瓶颈。

  4. **理解索引:**EXPLAIN 是优化索引最有力的工具。深刻理解最左前缀原则、覆盖索引、索引选择性等概念。

  5. **结合慢查询日志:**将 EXPLAIN 用于分析慢查询日志中捕获的语句。

  6. **利用格式化输出:**使用 FORMAT=JSON 或 FORMAT=TREE (MySQL) 或 FORMAT JSON (PG) 获取更详细、结构化的信息,便于程序解析或可视化工具展示。

  7. **版本差异:**不同数据库版本,EXPLAIN 的输出格式和含义可能略有变化,查阅官方文档对应版本。

七、总结

EXPLAIN 不是 SQL 性能优化的终点,而是起点和指路明灯。它让你摆脱了"盲目调优"的困境,提供了数据库内部执行逻辑的宝贵洞察。掌握解读 EXPLAIN 计划的能力,是每一个追求高性能数据库应用的开发者、DBA 的必备技能。

下次当你的 SQL 慢如蜗牛时,别急着抓狂,深吸一口气,敲下 EXPLAIN,让执行计划告诉你性能瓶颈在哪里!优化之旅,由此启程。

相关推荐
两圆相切39 分钟前
Oracle触发器:数据世界的“隐形守护者“
数据库·oracle
掘根2 小时前
【MySQL进阶】三大范式
数据库·mysql·oracle
认真就输6 小时前
性能优化:两条SQL索引优化,CPU占用率从40%降至25%
性能优化
apocelipes7 小时前
使用uint64_t批量比较短字符串
c语言·数据结构·c++·算法·性能优化·golang
经典19928 小时前
mysql 性能优化之Explain讲解
android·mysql·性能优化
张帅涛_6668 小时前
Golang中GROM多表关联跟原生SQL多表关联区别
开发语言·sql·golang
大大大水蜜桃9 小时前
sql练习二
java·数据库·sql
ycllycll11 小时前
hive的sql优化思路-明白底层运行逻辑
hive·hadoop·sql
kp0000012 小时前
【web安全】SQL注入与认证绕过
sql·安全·web安全