去年遇到过一个生产环境慢 SQL

sql

-- 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 怎么用

sql

EXPLAIN SELECT * FROM customer WHERE cust_id = 123;-- 或EXPLAIN ANALYZE SELECT * FROM customer WHERE cust_id = 123;  -- MySQL 8.0+

返回结果长这样(简化版):

id type possible_keys key rows Extra
1 const PRIMARY PRIMARY 1 -

核心字段就 3 个:

字段 含义 重点看
type 访问类型(最重要!) 7 个等级,从优到劣排序
key 实际使用的索引 NULL = 没走索引
rows 预估扫描行数 数字越大越慢
1.3 type 字段:7 个等级速查

从最优到最差排序

等级 含义 性能
system 表只有一行(系统表) ⭐⭐⭐⭐⭐
const 主键 / 唯一索引等值查询 ⭐⭐⭐⭐⭐
eq_ref JOIN 时主键 / 唯一索引匹配 ⭐⭐⭐⭐
ref 非唯一索引等值查询 ⭐⭐⭐
range 索引范围扫描 ⭐⭐
index 全索引扫描
ALL 全表扫描 ❌ 必须消除

金融项目调优目标绝不允许出现 ALL,能避免 index 也避免

二、7 个等级逐一拆解(金融项目视角)

2.1 system - 系统表(极少用)

含义:表里只有一行数据(系统表、information_schema 等)。

sql

EXPLAIN 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

为什么是 const? MySQL 把这个常量值直接当作"必返回 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_ref

  • eq_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-01'),type 也会是 range,但 rows 可能很大。

    2.6 index - 全索引扫描

    含义遍历整个索引树(不是遍历表数据,是遍历索引)。

    sql

    EXPLAIN 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 - 全表扫描(必须消除!)

    含义遍历表的所有数据(从磁盘读所有行),最差的性能。

    sql

    EXPLAIN SELECT * FROM customer WHERE cust_name = '张三';-- type: ALL  (如果 cust_name 没建索引)

    金融项目里 ALL 出现的原因(按频率排)

    1.WHERE 条件字段没建索引(最常见)

    2.函数 / 表达式导致索引失效(如 WHERE DATE(create_time) = '2026-06-09'

    3.LIKE '%xxx%' 前导通配(如 WHERE cust_name LIKE '%张三%'

    4.数据类型不匹配(字段是 VARCHAR,传入 INT)

    5.OR 条件部分无索引(如 WHERE a=1 OR b=2,b 没建索引)

    6.数据量小被优化器放弃索引(表 < 几十行,ALL 更快)

      三、金融项目调优实战:从 12 秒到 200ms

      回到开头的慢 SQL:

      sql

      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  );

      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 步:重写 SQL(IN 转 JOIN)

      sql

      -- 原 SQL(IN 子查询)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  );-- 改写后(JOIN)SELECT 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 步:再看 EXPLAIN

      id table type key rows Extra
      1 b ref idx_blacklist_active 50 Using index
      1 cp ref idx_branch_period 8000 Using where

      customer_performance 表从 ALL 降到 ref。rows 从 1000w+ 降到 8000。

      3.2 效果对比
      指标 调优前 调优后 提升
      type ALL ref 6 个等级
      rows 1000w+ 8000 1250 倍
      耗时 12s 200ms 60 倍
      索引使用 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 看 type,ALL 必须优化。

      步骤 2:让 ref → eq_ref / const(次高优先级)
      现状 目标 手段
      ref const 业务上按 ID 查单条 + 改写
      ref eq_ref JOIN 关联字段加唯一索引
      range ref 范围查询改成等值查询
      步骤 3:减少 range 的范围(中等优先级)
      • 业务上分批查(按月分批)
      • 加更精确的索引
      • 用覆盖索引避免回表
      步骤 4:避免 index 全索引扫描(最低优先级)
      • 检查 SELECT 字段能否走覆盖索引
      • 检查 ORDER BY 能否走索引排序

      五、EXPLAIN 必看 3 个字段 + Extra 解读

      5.1 必看 3 个字段

      sql

      EXPLAIN SELECT * FROM customer WHERE cust_id = 123;
      字段 健康值 危险值
      type const / eq_ref / ref ALL
      key 有索引名 NULL
      rows 越小越好 > 10w+ 警惕
      5.2 Extra 字段(高级诊断)
      Extra 含义 优化建议
      Using where 用了 WHERE 过滤 正常
      Using index 覆盖索引,不用回表 ✅ 优秀
      Using filesort 文件排序(非索引排序) ⚠️ 加索引或调 SQL
      Using temporary 使用临时表(GROUP BY / DISTINCT) ⚠️ 优化 GROUP BY
      Using join buffer JOIN 没走索引,被驱动表 join buffer ⚠️ 加索引
      Impossible WHERE WHERE 永远 false 检查 SQL 逻辑
      Select tables optimized away MySQL 直接优化掉,不需要执行 ✅ 最好

      金融项目里 Using filesortUsing 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 查单条必须 const,JOIN 关联字段必须 eq_ref"

      "WHERE 字段没建索引是 ALL 第一大原因"

      "Using filesort 和 Using temporary 是报表慢的元凶"

      Logo

      AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

      更多推荐