大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!
你肯定用过 EXPLAIN 看 SQL 的执行计划,但你有没有真正看全过?type 到底有几种取值?Extra 里的 Using index、Using where、Using temporary、Using filesort 分别什么意思?key_len 怎么算?filtered 有什么用?今天我们就来把 EXPLAIN 的输出彻底讲透。
type相当于分拣效率:最快的是“直接按门牌号送”(const),最慢的是“翻遍整个仓库”(ALL)。possible_keys= 可能用的传送带,key= 实际选的传送带。rows= 需要检查的包裹数量。filtered= 初步分拣后还需要人工二次分拣的比例。Extra= 额外操作标记,如“用了传送带但还要人工挑拣”(Using where)、“需要临时堆货”(Using temporary)。
一、EXPLAIN 输出列完整解读
我们用 EXPLAIN SELECT ... 会得到一张表,每个列的含义如下:
| 列名 | 含义 | 关键点 |
| id | SELECT 的标识序号 | 越大越先执行;相同则从上到下 |
| select_type | 查询类型 | SIMPLE、PRIMARY、SUBQUERY、DERIVED、UNION 等 |
| table | 表名或别名 | 可能是临时表名() |
| partitions | 匹配的分区 | 分区表时有用 |
| type | 连接类型(重要) | 性能从好到差:system > const > eq_ref > ref > range > index > ALL |
| possible_keys | 可能使用的索引 | 列出候选索引 |
| key | 实际使用的索引 | 如果 NULL 表示没用到索引 |
| key_len | 使用索引的长度(字节) | 判断联合索引用了多少列 |
| ref | 索引列与哪个值比较 | 常量 const 或 列名 |
| rows | 预估需要扫描的行数 | 越大越差 |
| filtered | 存储引擎返回的行中满足剩余条件的比例 | 100% 最好 |
| Extra | 额外信息 | Using index、Using where、Using temporary、Using filesort 等 |
二、type 详解:性能的关键指标
type 表示 MySQL 如何查找表中的行,按性能从最优到最差排序:
| type | 含义 | 示例 | 出现条件 |
| system | 系统表,只有一行 | 极少见 | 系统表或 const 的特例 |
| const | 最多匹配一行,用主键或唯一索引等值查询 | WHERE id = 1 |
主键或唯一索引,且查询结果为常量 |
| eq_ref | 使用唯一索引进行关联,每个关联只返回一行 | JOIN ... ON t1.id = t2.id 且 t2.id 是主键 |
被驱动表使用主键或唯一索引连接 |
| ref | 使用非唯一索引或前缀索引进行等值匹配 | WHERE name = 'abc'(name 有普通索引) |
索引列不是唯一或可为 NULL |
| range | 索引范围扫描 | WHERE id BETWEEN 1 AND 100 或 IN、>、< |
索引列上的范围条件 |
| index | 全索引扫描 | 索引覆盖但没过滤条件 | 遍历整个索引树 |
| ALL | 全表扫描(最差) | 无索引或优化器认为全表更快 | 大表且无有效索引 |
优化目标:至少达到 range 级别,争取达到 ref 或 const。
案例:
-- type = ALL 很差 EXPLAIN SELECT * FROM orders WHERE amount > 100; -- 添加索引后 type 变为 range ALTER TABLE orders ADD INDEX idx_amount(amount);
三、Extra 详解:优化器还做了什么?
Extra 列包含关于查询执行的额外信息,很多关键优化线索都在这里:
| Extra 信息 | 含义 | 优劣 | 优化方向 |
| Using index | 使用了覆盖索引,不回表 | ✅ 好 | 继续保持 |
| Using where | 存储引擎返回后在 Server 层过滤 | 🟡 普通 | 尝试将过滤条件移到索引中 |
| Using temporary | 使用了临时表(通常用于 GROUP BY 或 DISTINCT) | ⚠️ 差 | 优化 GROUP BY/ORDER BY 或加索引 |
| Using filesort | 需要额外排序,不能利用索引排序 | ⚠️ 差 | 对 ORDER BY 列加索引 |
| Using index condition | 使用索引下推(ICP) | ✅ 好 | MySQL 5.6+ 自动优化 |
| Using join buffer | 连接使用了 Buffer(Block Nested Loop) | 🟡 普通 | 加索引避免 Buffer |
| Impossible WHERE | WHERE 条件永远为假 | 无需优化 | 检查 SQL 逻辑 |
| No tables used | 没有 FROM 或 FROM DUAL | - | - |
注意:Using filesort 不是真的用文件,而是指无法利用索引排序,需要在内存或磁盘中排序。当排序结果集大时很慢。
案例:
-- Using filesort EXPLAIN SELECT * FROM orders ORDER BY create_time; -- 加索引后 Using filesort 消失 ALTER TABLE orders ADD INDEX idx_create_time(create_time);
四、组合索引与 key_len 实战
key_len 表示 MySQL 在索引中实际使用的字节数。通过它可判断联合索引使用了多少列。
计算规则:
- 列长度:INT=4, BIGINT=8, DATE=3, TIMESTAMP=4, CHAR(n)=n×字符集字节数(utf8mb4=4),VARCHAR(n)=n×4+2。
- 允许 NULL 额外 +1。
示例:索引 (user_id, log_date, type),user_id INT NOT NULL (4),log_date DATE NOT NULL (3),type TINYINT (1)。查询 WHERE user_id=1 AND log_date='2026-06-01' 则 key_len=4+3=7,说明用到了前两列。
联合索引使用原则:最左前缀,且中间的列不能跳过。如果跳过了某列,后面的列不会被使用。
五、filtered 的作用
filtered 表示存储引擎返回的行中,满足剩余 WHERE 条件的比例(估算)。100% 表示所有返回行都满足条件。如果 filtered 很小(如 10%),说明索引过滤后还要过滤掉 90% 的行,回表成本高。
用法:在 JOIN 中,驱动表的 filtered 值直接影响被驱动表的读取次数。
六、实战案例优化全过程
原始 SQL:
SELECT * FROM orders WHERE customer_id = 12345 AND status = 'PAID' AND create_time > '2026-01-01' ORDER BY create_time DESC LIMIT 10;
原执行计划:type=ref,key=customer_id,rows=1000,Extra="Using where; Using filesort"。
问题分析:
- 用了 customer_id 索引,但 status 和 create_time 过滤在回表后执行。
- filesort 因为 create_time 没在索引中用于排序。
优化方案:建立联合索引 (customer_id, status, create_time)。
新执行计划:type=ref,key=联合索引,key_len=4+?+3,Extra=无 filesort(因为索引已排序)。
效果:查询从 0.5 秒降到 0.02 秒。
七、总结与实用检查清单
阅读 EXPLAIN 时按以下顺序检查:
- type:是否出现了 ALL 或 index?如果是,考虑加索引。
- key:是否为 NULL?是则索引没用上。
- rows:是否远大于预期?检查索引选择性。
- Extra:是否出现 Using temporary 或 Using filesort?优化排序和分组。
- filtered:是否低于 30%?检查索引是否能覆盖更多过滤条件。
小耶在手,SQL 不愁
还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~