概要
在之前HTT的面试中,面试官问了下SQL优化的事项,但是对于查询计划的回答并不是很好,所以在这里记录一下关于SQL explain的一些事项
EXPLAIN的作用
explain是SQL性能优化的关键工具,其内容包含了数据库如何具体执行一条SQL语句,通过分析其返回的执行计划,可以了解如何去执行一次查询,从而定位性能瓶颈,进行优化
基本用法
如下所示
sql
EXPLAIN SELECT * FROM users WHERE name = 'John';
执行计划的关键字段
| 字段 | 重要性 | 说明与解读 |
|---|---|---|
| type | ⭐⭐⭐ (最关键) | 访问类型 ,即 MySQL 找到所需行的方式。性能从优到劣常见为:system > const > eq_ref > ref > range > index > ALL。**ALL(全表扫描)**是需要极力避免的情况。 |
| key | ⭐⭐⭐ | 实际使用的索引。如果此列为 NULL,则意味着查询没有使用索引,这通常是性能问题的信号。 |
| rows | ⭐⭐ | MySQL 预估需要扫描的行数。这是一个基于统计信息的预估值,数值越小越好。 |
| Extra | ⭐⭐⭐ | 额外信息 ,包含大量重要细节。这里可能会出现需要警惕的提示,如 Using filesort(需要额外排序)和 Using temporary(使用了临时表);也可能出现好的提示,如 Using index(使用覆盖索引,无需回表)。 |
| possible_keys | ⭐⭐ | 查询可能使用的索引。 |
| select_type | ⭐⭐ | 查询的类型,如简单查询(SIMPLE)、子查询(SUBQUERY)、联合查询(UNION)等。 |
一般而言,我们在工作中要看的是type,是否走了全表扫描,如果是全表扫描的话,性能可能会变得非常低,所以我们一般要避免这种情况发生。
除此之外,如果看到rows比较大的话,也有存在优化的可能。
select_type也是很重要的指标。
Type的类型以及详细解析
| 性能等级 | 类型 (type) | 说明与解读 |
|---|---|---|
| 🟢 最优 | system |
表只有一行(系统表)。这是 const 类型的一个特例。 |
| 🟢 最优 | const |
通过主键或唯一索引进行等值查询,结果最多只有一行匹配。 |
| 🟢 最优 | eq_ref |
在多表连接中,使用主键或唯一索引作为关联条件,对于前表的每一行,后表只有一条记录与之匹配。 |
| 🟡 较优 | ref |
使用非唯一索引进行等值查询,返回匹配某个单独值的所有行。 |
| 🟡 中等 | fulltext |
使用全文索引进行搜索。 |
| 🟠 一般 | ref_or_null |
类似 ref,但查询条件中额外包含对 NULL 值的查找。 |
| 🟠 一般 | index_merge |
查询同时使用了多个索引,并对结果进行合并(取交集或并集)。 |
| 🟠 一般 | unique_subquery |
在一些 IN 子查询中,替换 eq_ref 类型,用于根据主键或唯一索引进行子查询。 |
| 🟠 一般 | index_subquery |
与 unique_subquery 类似,但用于非唯一索引的子查询。 |
| 🔴 较差 | range |
使用索引进行范围扫描,例如使用 BETWEEN、IN、>、< 等操作符。 |
| 🔴 较差 | index |
全索引扫描,遍历整个索引树。虽然比全表扫描好,但数据量大时性能依然较低。 |
| ❌ 最差 | ALL |
全表扫描,没有使用任何索引。这是性能最差的情况,需要尽力避免。 |
通常情况下,我们希望SQL查询至少要达到range水平,争取达到ref,但是就从国内大部分从业者的水平来说,能有个range或者fulltext就烧高香了。
重点类型详解与一些示例
1、const,eq_ref和ref
这三个是高效查询中最常见的类型,区分它们的关键在于索引的唯一性
const:通过主键或者唯一索引定位唯一一行记录
sql
EXPLAIN SELECT * FROM users WHERE id = 1; -- id 是主键[5](@ref)
eq_ref:在多表连接时,连接条件使用了被驱动表的主键或者唯一索引
sql
-- 假设 orders 表的 user_id 字段与 users 表的主键 id 关联
EXPLAIN SELECT * FROM orders o JOIN users u ON o.user_id = u.id; -- u.id 是主键,对于 o 的每一行,u 只有一条匹配[2,6](@ref)
ref:使用普通索引进行等值查询,可能返回多行记录
sql
EXPLAIN SELECT * FROM users WHERE name = 'Alice'; -- name 字段建有普通索引[1,7](@ref)
2、range
这是处理范围查询时能达到的最好类型,常见于使用了between,大于号,小于号,in等操作符的查询
sql
EXPLAIN SELECT * FROM users WHERE age BETWEEN 18 AND 30; -- age 字段有索引[4](@ref)
EXPLAIN SELECT * FROM users WHERE id IN (1, 3, 5); -- 主键的 IN 查询[3](@ref)
3、index与all
这两种类型都需要扫描大量数据,是优化的重点对象
index:虽然也是全扫描,但是扫描的是索引树,在某些情况下(覆盖索引)效率还行。
sql
-- 假设 birth_year 和 gender 是一个复合索引
EXPLAIN SELECT birth_year, gender FROM users; -- 查询的列都包含在索引中(覆盖索引),避免了回表[4](@ref)
all:全表扫描
不用说什么了,最差的情况
sql
EXPLAIN SELECT * FROM users WHERE active = 0; -- active 字段没有索引[6](@ref)
如何优化type类型
优化目标应该是尽量避免出现 ALL和 index ,并努力让查询达到 range及以上级别。
-
为查询条件添加索引 :这是最直接有效的方法。确保
WHERE、JOIN、ORDER BY子句中的字段有合适的索引。 -
避免索引失效的写法 :例如,避免在索引字段上使用函数或表达式(如
WHERE id + 1 = 5)、避免前缀模糊匹配(如LIKE '%雪')、注意数据类型不一致导致的隐式转换。 -
使用覆盖索引:让索引包含查询所需的所有字段,这样可以避免回表操作,提升查询速度。
SELECT_TYPE类型
| 类型 | 说明与解读 |
|---|---|
| SIMPLE | 简单的 SELECT 查询,不包含子查询或 UNION 操作。 |
| PRIMARY | 查询中最外层的 SELECT。如果查询包含子查询或 UNION,最外层的查询就会被标记为 PRIMARY。 |
| SUBQUERY | 在 SELECT 或 WHERE 列表中包含的子查询,该子查询不依赖于外部的查询。 |
| DEPENDENT SUBQUERY | 与 SUBQUERY 类似,但此子查询依赖于外部查询的结果集。需要特别注意,因为它可能对外部查询的每一行结果都执行一次,容易导致性能问题。 |
| DERIVED | 出现在 FROM 子句中的子查询(也称为派生表),MySQL 会递归执行这些子查询,把结果放在临时表里。 |
| UNION | 在 UNION 查询中,第二个及之后的 SELECT 语句会被标记为 UNION。 |
| DEPENDENT UNION | 与 UNION 类似,但出现在子查询中,且 UNION 的第二个或之后的查询依赖于外部查询。 |
| UNION RESULT | 从 UNION 操作的临时表(即合并后的结果集)中获取结果的 SELECT。 |
重点类型详解与示例
理解这些类型如何在实际查询中体现非常重要。
-
PRIMARY与SUBQUERY/DERIVED在一个包含子查询的语句中,最外层的 SELECT是
PRIMARY。如果子查询在FROM子句中,它会被标记为DERIVED(派生表),MySQL 需要先执行这个子查询,将结果存入临时表,再对外部查询进行处理 。如果子查询在WHERE子句中且独立于外部查询,则通常标记为SUBQUERY。 -
DEPENDENT SUBQUERY的性能关注点这种类型的子查询其执行依赖于外部查询的每一行结果。这意味着,如果外部查询返回了1000行数据,这个子查询就可能需要执行1000次。当数据量很大时,这会严重消耗性能,是需要重点优化的类型之一 。
如何结合使用
分析 EXPLAIN时,最好将 select_type与 id字段结合来看。id值越大,执行优先级越高。如果 id相同,则执行顺序从上到下 。通过观察不同 id和 select_type的组合,可以清晰地勾勒出整个复杂查询的执行步骤。
小结
这段时间十分压抑,三角洲打上了统帅,但是打太多觉得还是空虚,觉得还是写一写什么吧。