3. EXPLAIN 执行计划:输出列详解与 type 性能排序
在 Go 项目慢查询定位中,EXPLAIN 是分析 SQL 执行计划的核心工具。它揭示了 MySQL 优化器如何访问表、使用索引以及连接数据。理解每一列的含义,尤其是 type 列的访问方式,是连接池调优前必须掌握的基础。
3.1 EXPLAIN 输出列详解
执行 EXPLAIN SELECT ... 后,MySQL 返回一行或多行结果,每行对应一个表(或子查询)。以下是最关键的 7 个输出列及其含义:
| 列名 | 含义 | 调优关注点 |
|---|---|---|
| id | 查询中 SELECT 的标识符,数字越大优先级越高;相同 id 表示同一层级 | id 递增顺序通常为执行顺序;id 相同则从上到下执行 |
| select_type | SELECT 的类型,如 SIMPLE、PRIMARY、SUBQUERY、DERIVED、UNION | 出现 DEPENDENT SUBQUERY 或 UNCACHEABLE SUBQUERY 时需警惕性能 |
| table | 输出行对应的表名或别名 | 可确认是否访问了预期表,避免多余联表 |
| type | 访问表的方式,是性能排序的核心指标(见 3.2 节) | 目标是至少达到 ref 或 range,避免 ALL |
| possible_keys | MySQL 认为可能用到的索引列表 | 若为 NULL,说明无可用索引,需考虑加索引 |
| key | 实际选用的索引名称 | 若为 NULL,说明未使用索引;若与 possible_keys 不同,可能是优化器选择了更优索引 |
| rows | MySQL 估计需要扫描的行数(估算值,非精确值) | 数值越大,查询越重;与连接池连接占用时间正相关 |
| Extra | 额外信息,如 Using index、Using where、Using filesort、Using temporary | 出现 Using filesort 或 Using temporary 通常意味着需要优化 |
调优提示: 在 Go 项目慢查询日志中,重点关注
rows 值远大于实际返回行数的查询,以及 Extra 中包含 Using filesort 或 Using temporary 的语句。这些往往是连接池连接被长时间占用的根源。
3.2 常见 type 类型及性能排序
type 列是 EXPLAIN 输出中最重要的性能指标,它描述了 MySQL 如何查找表中的行。性能从最优到最差排序如下:
| 性能排序 | type 值 | 含义 | 典型场景 |
|---|---|---|---|
| 1(最优) | system | 表只有一行(系统表),是 const 的特例 | 极少出现,通常为系统表 |
| 2 | const | 通过主键或唯一索引等值匹配,最多返回一行 | WHERE id = 1 |
| 3 | eq_ref | 联表时,被驱动表使用主键或唯一索引等值匹配 | JOIN ... ON t1.id = t2.user_id |
| 4 | ref | 使用非唯一索引或前缀索引等值匹配,返回多行 | WHERE status = 'active' |
| 5 | range | 使用索引进行范围扫描,如 BETWEEN、>、<、IN | WHERE age BETWEEN 18 AND 30 |
| 6 | index | 扫描整个索引树,但不需要回表(覆盖索引) | SELECT id, name FROM user(id+name 联合索引) |
| 7(最差) | ALL | 全表扫描,性能最差 | 无索引或查询条件无法使用索引 |
性能红线: 在连接池调优场景中,
ALL(全表扫描)是必须消除的。一个 ALL 查询可能扫描数十万行,导致单个连接被占用数秒甚至数十秒,直接引发连接池耗尽和请求排队。
3.3 type 性能对比 SVG 图
下图直观展示了从 system 到 ALL 的性能递减趋势,以及每种访问方式对应的扫描范围特征:
3.4 实战:通过 EXPLAIN 识别慢查询根因
在 Go 项目连接池调优中,EXPLAIN 的输出直接指导优化方向。以下是一个典型慢查询的 EXPLAIN 分析示例:
-- 慢查询日志中捕获的 SQL
EXPLAIN SELECT o.id, o.amount, u.name
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.status = 'PENDING'
ORDER BY o.created_at DESC
LIMIT 20;
假设 EXPLAIN 输出如下:
| id | select_type | table | type | possible_keys | key | rows | Extra |
|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | o | ALL | NULL | NULL | 150000 | Using where; Using filesort |
| 1 | SIMPLE | u | eq_ref | PRIMARY | PRIMARY | 1 | Using index |
分析结论:
•
•
•
• 优化方案: 在
•
orders 表出现 ALL(全表扫描),扫描 15 万行,且 possible_keys 为 NULL,说明 status 列无索引。•
Extra 包含 Using filesort,说明 created_at 排序也无法使用索引。•
users 表使用 eq_ref 通过主键关联,性能良好。• 优化方案: 在
orders 表创建 (status, created_at) 联合索引,可将 type 提升至 ref 或 range,消除 filesort,扫描行数降至数百行。
3.5 Extra 列关键标识速查
Extra 列中的常见标识及其对连接池性能的影响:
| Extra 值 | 含义 | 对连接池的影响 |
|---|---|---|
| Using index | 覆盖索引,无需回表 | ✅ 最优,连接占用时间短 |
| Using where | 使用 WHERE 条件过滤 | ✅ 正常,需结合 type 判断 |
| Using index condition | 索引条件下推(ICP) | ✅ 减少回表次数 |
| Using filesort | 需要额外排序操作 | ⚠️ 可能增加连接占用时间 |
| Using temporary | 使用临时表(如 GROUP BY 无索引) | ⚠️ 严重,可能大量占用内存和磁盘 |
| Using where; Using index | 在索引上过滤并覆盖 | ✅ 理想状态 |
连接池视角: 一个包含
Using temporary 和 Using filesort 的查询,可能将单次连接占用时间从毫秒级提升到秒级。在连接池最大连接数固定的情况下,这类查询是导致连接池饥饿的主要元凶。