MySQL执行计划工具EXPLAIN
去年遇到过一个生产环境慢 SQLsql-- 27 家分行的报表查询原本要 12 秒SELECT *FROM customer_performance cpWHERE cp.branch_id SH001 AND cp.period 2025Q4 AND cp.cust_id IN ( SELECT cust_id FROM blacklist WHERE active 1 );分行运营投诉报表打开要等 12 秒严重影响客户经理体验。我用EXPLAIN一查type ALL全表扫描。1000w 行的表做全表扫描不慢才怪。加索引、改写 SQL、调 MySQL 参数优化后 12 秒降到 200ms。这个故事里EXPLAIN 是核心工具。type字段是 EXPLAIN 的灵魂。今天这篇文章就带大家彻底搞懂 EXPLAIN type 的 7 个等级以及金融项目里怎么读 EXPLAIN。一、EXPLAIN 是什么3 分钟搞懂1.1 一句话定义EXPLAIN 是 MySQL 提供的 SQL 执行计划分析工具告诉你 MySQL 打算怎么执行这条 SQL走没走索引、走了哪个索引、有没有全表扫描。1.2 怎么用sqlEXPLAIN SELECT * FROM customer WHERE cust_id 123;-- 或EXPLAIN ANALYZE SELECT * FROM customer WHERE cust_id 123; -- MySQL 8.0返回结果长这样简化版idtypepossible_keyskeyrowsExtra1constPRIMARYPRIMARY1-核心字段就 3 个字段含义重点看type访问类型最重要7 个等级从优到劣排序key实际使用的索引NULL 没走索引rows预估扫描行数数字越大越慢1.3 type 字段7 个等级速查从最优到最差排序等级含义性能system表只有一行系统表⭐⭐⭐⭐⭐const主键 / 唯一索引等值查询⭐⭐⭐⭐⭐eq_refJOIN 时主键 / 唯一索引匹配⭐⭐⭐⭐ref非唯一索引等值查询⭐⭐⭐range索引范围扫描⭐⭐index全索引扫描⭐ALL全表扫描❌ 必须消除金融项目调优目标绝不允许出现 ALL能避免 index 也避免。二、7 个等级逐一拆解金融项目视角2.1 system - 系统表极少用含义表里只有一行数据系统表、information_schema 等。sqlEXPLAIN SELECT * FROM mysql.user WHERE Host localhost AND User root;-- type: system实际意义基本只在系统表里出现。金融项目里几乎遇不到。2.2 const - 主键/唯一索引等值查询金融最高频含义通过主键或唯一索引做等值查询最多返回 1 行。sql-- 主键等值EXPLAIN SELECT * FROM customer WHERE cust_id 123456;-- type: const-- 唯一索引等值EXPLAIN SELECT * FROM account WHERE account_no 6225881234567890;-- type: const为什么是 constMySQL 把这个常量值直接当作必返回 1 行处理扫描复杂度 O(1)。金融最高频的场景客户表按 cust_id 查账户表按 account_no 查订单表按 order_id 查口诀业务上按 ID 查单条的 SQL必须走到 const。否则就是缺主键或缺唯一索引。2.3 eq_ref - JOIN 时主键/唯一索引匹配含义JOIN 关联时被驱动表通过主键或唯一索引匹配。sql-- 客户表 1 条 账户表 N 条EXPLAIN SELECT c.cust_name, a.account_noFROM customer cJOIN account a ON a.cust_id c.cust_idWHERE c.cust_id 123456;-- 账户表 type: eq_ref用 cust_id 索引匹配特点JOIN 的标杆性能每个被驱动表最多 1 次索引查找金融项目关联查询的目标态典型场景客户 → 账户1:N 关联订单 → 客户N:1 关联分行 → 客户经理1:N 关联注意如果 JOIN 字段没建索引或不是唯一索引会降级到 ALL全表扫描性能断崖式下跌。2.4 ref - 非唯一索引等值查询含义通过非唯一索引做等值查询可能返回多行。sql-- branch_id 是普通索引不唯一EXPLAIN SELECT * FROM customer WHERE branch_id SH001;-- type: ref对比 eq_refeq_ref唯一索引 →1 行ref非唯一索引 →多行但都在索引树内金融项目典型场景按branch_id查客户一个分行 N 个客户按period查业绩一个季度 N 条记录按status查订单多种状态共存的非唯一索引性能评估rows 越少越好。如果 rows 接近全表100w考虑1.加更精确的索引2.用覆盖索引SELECT字段都在索引里3.业务上分批查询2.5 range - 索引范围扫描含义通过索引做范围查询BETWEEN、、、IN、LIKE xxx%。sql-- 时间范围EXPLAIN SELECT * FROM trade_log WHERE trade_time BETWEEN 2026-01-01 AND 2026-06-01;-- type: range-- INEXPLAIN SELECT * FROM customer WHERE branch_id IN (SH001, SH002, SH003);-- type: range-- 范围EXPLAIN SELECT * FROM account WHERE balance 100000;-- type: range金融项目高频场景交易流水按时间范围查客户按状态 / 等级查账户按余额范围查报表按期次查性能关键范围越小越好。如果范围是全表WHERE trade_time 2020-01-01type 也会是 range但 rows 可能很大。2.6 index - 全索引扫描含义遍历整个索引树不是遍历表数据是遍历索引。sqlEXPLAIN SELECT cust_id FROM customer; -- SELECT 字段都在索引里-- type: index为什么不是 ALL因为查询字段都在索引里MySQL 不用回表不用去主键索引查完整数据只扫索引树就够了。什么时候会遇到SELECT字段都在某个索引里ORDER BY用索引排序GROUP BY用索引分组比 ALL 好但比 range 差index扫整个索引树100w 索引条目range扫索引的一部分10w 索引条目金融项目典型场景报表查COUNT(*)配合覆盖索引按某个字段全排序2.7 ALL - 全表扫描必须消除含义遍历表的所有数据从磁盘读所有行最差的性能。sqlEXPLAIN SELECT * FROM customer WHERE cust_name 张三;-- type: ALL 如果 cust_name 没建索引金融项目里 ALL 出现的原因按频率排1.WHERE 条件字段没建索引最常见2.函数 / 表达式导致索引失效如WHERE DATE(create_time) 2026-06-093.LIKE %xxx% 前导通配如WHERE cust_name LIKE %张三%4.数据类型不匹配字段是 VARCHAR传入 INT5.OR 条件部分无索引如WHERE a1 OR b2b 没建索引6.数据量小被优化器放弃索引表 几十行ALL 更快三、金融项目调优实战从 12 秒到 200ms回到开头的慢 SQLsqlSELECT *FROM customer_performance cpWHERE cp.branch_id SH001 AND cp.period 2025Q4 AND cp.cust_id IN ( SELECT cust_id FROM blacklist WHERE active 1 );EXPLAIN 结果customer_performance 表 type ALL全表扫描blacklist 表 type ref用 active 索引性能瓶颈customer_performance 表 1000w 行每次都全表扫。3.1 调优步骤第 1 步看 WHERE 条件条件字段选择性建议branch_id SH001一家分行 1/27联合索引第 1 列period 2025Q4一个季度 1/N联合索引第 2 列cust_id IN (...)黑名单 1-100子查询条件第 2 步建联合索引sql-- 把 WHERE 高频条件做成联合索引CREATE INDEX idx_branch_period ON customer_performance(branch_id, period);-- 黑名单表CREATE INDEX idx_blacklist_active ON blacklist(active, cust_id);第 3 步重写 SQLIN 转 JOINsql-- 原 SQLIN 子查询SELECT cp.*FROM customer_performance cpWHERE cp.branch_id SH001 AND cp.period 2025Q4 AND cp.cust_id IN ( SELECT cust_id FROM blacklist WHERE active 1 );-- 改写后JOINSELECT cp.*FROM customer_performance cpINNER JOIN blacklist b ON cp.cust_id b.cust_idWHERE cp.branch_id SH001 AND cp.period 2025Q4 AND b.active 1;第 4 步再看 EXPLAINidtabletypekeyrowsExtra1brefidx_blacklist_active50Using index1cprefidx_branch_period8000Using wherecustomer_performance 表从 ALL 降到 ref。rows 从 1000w 降到 8000。3.2 效果对比指标调优前调优后提升typeALLref6 个等级rows1000w80001250 倍耗时12s200ms60 倍索引使用无idx_branch_period提升四、金融项目调优优先级4 步走步骤 1消除 ALL最高优先级sql-- 找出所有慢 SQLSELECT * FROM information_schema.PROCESSLIST WHERE COMMAND Query AND TIME 5; -- 执行 5 秒的查询-- 或用慢查询日志SET GLOBAL slow_query_log ON;SET GLOBAL long_query_time 2; -- 超过 2 秒算慢找到后 EXPLAIN 看 typeALL 必须优化。步骤 2让 ref → eq_ref / const次高优先级现状目标手段refconst业务上按 ID 查单条 改写refeq_refJOIN 关联字段加唯一索引rangeref范围查询改成等值查询步骤 3减少 range 的范围中等优先级业务上分批查按月分批加更精确的索引用覆盖索引避免回表步骤 4避免 index 全索引扫描最低优先级检查SELECT字段能否走覆盖索引检查ORDER BY能否走索引排序五、EXPLAIN 必看 3 个字段 Extra 解读5.1 必看 3 个字段sqlEXPLAIN SELECT * FROM customer WHERE cust_id 123;字段健康值危险值typeconst / eq_ref / refALLkey有索引名NULLrows越小越好 10w 警惕5.2 Extra 字段高级诊断Extra含义优化建议Using where用了 WHERE 过滤正常Using index覆盖索引不用回表✅ 优秀Using filesort文件排序非索引排序⚠️ 加索引或调 SQLUsing temporary使用临时表GROUP BY / DISTINCT⚠️ 优化 GROUP BYUsing join bufferJOIN 没走索引被驱动表 join buffer⚠️ 加索引Impossible WHEREWHERE 永远 false检查 SQL 逻辑Select tables optimized awayMySQL 直接优化掉不需要执行✅ 最好金融项目里Using filesort和Using temporary必须消除—— 这是报表慢的常见原因。六、动手验证你的 SQL 走到哪个等级sql-- 1. 找慢 SQLSHOW PROCESSLIST;-- 2. 找没有走索引的表SELECT * FROM information_schema.TABLESWHERE TABLE_SCHEMA 你的库名ORDER BY TABLE_ROWS DESCLIMIT 20; -- 找最大的表-- 3. EXPLAIN 你的 SQLEXPLAIN SELECT * FROM customer WHERE cust_name 张三;-- 4. 看 type 是不是 ALL是的话加索引CREATE INDEX idx_cust_name ON customer(cust_name);-- 5. 再 EXPLAIN 验证EXPLAIN SELECT * FROM customer WHERE cust_name 张三;-- 应该从 ALL 变成 ref七、总结口诀EXPLAIN type 7 等级system const eq_ref ref range index ALL金融项目绝不允许 ALL能避免 index 也避免按 ID 查单条必须 constJOIN 关联字段必须 eq_refWHERE 字段没建索引是 ALL 第一大原因Using filesort 和 Using temporary 是报表慢的元凶