💡 摘要:你是否曾经面对慢查询束手无策?是否想知道MySQL如何执行你的SQL语句?是否希望提前发现性能瓶颈?
EXPLAIN就是你的答案!这个看似简单的命令,却是MySQL查询优化的终极武器。它能揭示查询执行的每一个细节,让你像拥有X光透视眼一样看透数据库的执行计划。
本文将带你深度解析EXPLAIN的每一个输出字段,通过真实案例教你如何识别性能问题、优化查询策略,让你的数据库查询速度提升数倍!


一、EXPLAIN基础:为什么它是优化必备工具?

1. EXPLAIN的核心价值
应用场景 解决的问题 优化效果
慢查询分析 找出性能瓶颈 查询时间从秒级到毫秒级
索引优化 验证索引使用情况 减少90%的磁盘I/O
联表优化 优化JOIN顺序和方式 提升复杂查询性能
排序优化 消除filesort操作 排序操作加速10倍
2. EXPLAIN的基本用法

sql

-- 基本语法
EXPLAIN SELECT * FROM users WHERE email = 'user@example.com';

-- 详细格式(MySQL 8.0+)
EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE user_id = 1001;

-- 分析连接查询
EXPLAIN 
SELECT u.username, o.order_date, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.city = '北京' AND o.amount > 1000;

-- 与实际执行计划对比(MySQL 8.0+)
EXPLAIN ANALYZE SELECT * FROM products WHERE price > 100;

二、深度解析EXPLAIN输出字段

1. 核心字段详解表
字段 含义 优化意义 理想值
id 查询标识符 识别复杂查询中的执行顺序 数字越小优先级越高
select_type 查询类型 了解查询的复杂程度 SIMPLE, PRIMARY
table 访问的表 知道正在操作哪个表 表名或别名
partitions 匹配的分区 分区表优化 NULL(未分区)
type 访问类型 最重要的性能指标 const, eq_ref, ref, range
possible_keys 可能使用的索引 索引选择分析 实际使用的索引
key 实际使用的索引 索引使用验证 索引名称
key_len 索引长度 索引使用效率 越短越好
ref 索引匹配 连接条件分析 const, func, NULL
rows 预估扫描行数 性能关键指标 越小越好
filtered 过滤比例 条件过滤效率 100%为最佳
Extra 额外信息 优化重点区域 Using index, Using where
2. type访问类型深度解析

sql

-- 性能从优到劣排序:
-- 1. system: 系统表,只有一行数据
EXPLAIN SELECT * FROM mysql.user WHERE host = 'localhost' AND user = 'root';

-- 2. const: 主键或唯一索引等值查询
EXPLAIN SELECT * FROM users WHERE id = 1;

-- 3. eq_ref: 唯一索引关联查询
EXPLAIN SELECT * FROM orders o 
JOIN users u ON o.user_id = u.id 
WHERE o.order_id = 1001;

-- 4. ref: 非唯一索引等值查询
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';

-- 5. range: 索引范围查询
EXPLAIN SELECT * FROM orders WHERE user_id BETWEEN 1000 AND 2000;

-- 6. index: 全索引扫描
EXPLAIN SELECT COUNT(*) FROM users;

-- 7. ALL: 全表扫描(需要优化)
EXPLAIN SELECT * FROM users WHERE name LIKE '%john%';
3. Extra字段常见值解析

sql

-- 正面信息(良好):
-- • Using index: 使用覆盖索引
EXPLAIN SELECT id, email FROM users WHERE email = 'test@example.com';

-- • Using index condition: 索引下推优化
EXPLAIN SELECT * FROM users WHERE email LIKE 'test%' AND age > 25;

-- 负面信息(需要优化):
-- • Using filesort: 需要额外排序
EXPLAIN SELECT * FROM users ORDER BY created_at DESC;

-- • Using temporary: 使用临时表
EXPLAIN SELECT DISTINCT department FROM employees;

-- • Using where: 在存储引擎层后过滤
EXPLAIN SELECT * FROM users WHERE name LIKE '%john%';

三、实战案例:EXPLAIN优化慢查询

1. 案例一:缺失索引导致的全表扫描

sql

-- 原始查询(执行时间:2.1秒)
EXPLAIN SELECT * FROM orders WHERE user_id = 1001 AND status = 'completed';

-- EXPLAIN输出:
/*
id: 1
select_type: SIMPLE
table: orders
type: ALL
key: NULL
key_len: NULL
rows: 1000000
Extra: Using where
*/

-- 问题分析:全表扫描100万行数据
-- 解决方案:添加复合索引
CREATE INDEX idx_user_status ON orders(user_id, status);

-- 优化后EXPLAIN:
/*
id: 1
select_type: SIMPLE
table: orders
type: ref
key: idx_user_status
key_len: 8
rows: 15
Extra: Using index condition
*/

-- 优化效果:查询时间从2.1秒 → 0.002秒
2. 案例二:低效的排序操作

sql

-- 原始查询(执行时间:1.8秒)
EXPLAIN SELECT * FROM products 
WHERE category_id = 101 
ORDER BY price DESC 
LIMIT 20;

-- EXPLAIN输出:
/*
id: 1
select_type: SIMPLE
table: products
type: ref
key: idx_category
key_len: 4
rows: 5000
Extra: Using filesort
*/

-- 问题分析:虽然使用了索引,但需要filesort
-- 解决方案:创建支持排序的复合索引
CREATE INDEX idx_category_price ON products(category_id, price DESC);

-- 优化后EXPLAIN:
/*
id: 1
select_type: SIMPLE
table: products
type: ref
key: idx_category_price
key_len: 4
rows: 5000
Extra: Using index
*/

-- 优化效果:查询时间从1.8秒 → 0.015秒,消除filesort
3. 案例三:复杂的联表查询优化

sql

-- 原始查询(执行时间:3.5秒)
EXPLAIN 
SELECT u.username, o.order_date, p.product_name, oi.quantity
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
WHERE u.city = '上海' AND o.order_date > '2023-01-01';

-- EXPLAIN输出显示:
-- • users表: type=ALL (全表扫描)
-- • orders表: type=ALL 
-- • 需要优化连接顺序和索引

-- 解决方案:添加缺失索引并优化连接顺序
CREATE INDEX idx_user_city ON users(city);
CREATE INDEX idx_order_user_date ON orders(user_id, order_date);
CREATE INDEX idx_order_item_order ON order_items(order_id);
CREATE INDEX idx_product_id ON products(product_id);

-- 优化后EXPLAIN显示所有表都使用了索引,查询时间:3.5秒 → 0.2秒

四、EXPLAIN FORMAT=JSON深度解析

1. JSON格式的详细输出

sql

EXPLAIN FORMAT=JSON
SELECT u.username, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.email LIKE 'john%' AND o.amount > 1000;

-- JSON输出包含的关键信息:
{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "15.75"  -- 查询总成本
    },
    "nested_loop": [
      {
        "table": {
          "table_name": "u",
          "access_type": "range",  -- 访问类型
          "possible_keys": ["idx_email"],
          "key": "idx_email",
          "used_key_parts": ["email"],
          "rows_examined_per_scan": 50,
          "rows_produced_per_join": 50,
          "filtered": "100.00",
          "cost_info": {
            "read_cost": "5.25",
            "eval_cost": "5.00",
            "prefix_cost": "10.25"
          }
        }
      },
      {
        "table": {
          "table_name": "o",
          "access_type": "ref",
          "key": "idx_user_amount",
          "used_key_parts": ["user_id", "amount"],
          "rows_examined_per_scan": 10,
          "rows_produced_per_join": 5,
          "filtered": "50.00",
          "cost_info": {
            "read_cost": "5.00",
            "eval_cost": "0.50",
            "prefix_cost": "15.75"
          }
        }
      }
    ]
  }
}
2. 成本分析实战

sql

-- 通过成本分析选择最优索引
EXPLAIN FORMAT=JSON
SELECT * FROM orders 
WHERE user_id = 1001 OR status = 'completed';

-- 分析不同索引的成本差异:
-- 方案1: 使用索引合并 (cost: 25.30)
-- 方案2: 全表扫描 (cost: 18.50) ← 优化器选择这个
-- 方案3: 强制使用索引 (cost: 30.75)

-- 优化建议:创建更好的复合索引
CREATE INDEX idx_user_status ON orders(user_id, status);

五、EXPLAIN ANALYZE:实际执行计划分析

1. 实际执行时间分析(MySQL 8.0+)

sql

EXPLAIN ANALYZE
SELECT * FROM products 
WHERE category_id = 101 
AND price > 100 
ORDER BY created_at DESC 
LIMIT 10;

-- 输出结果:
/*
-> Limit: 10 row(s)  (actual time=15.25..15.30 rows=10 loops=1)
    -> Sort: products.created_at DESC, limit input to 10 row(s)  (actual time=15.24..15.28 rows=10 loops=1)
        -> Filter: (products.price > 100)  (actual time=0.125..14.50 rows=1500 loops=1)
            -> Index range scan on products using idx_category over (category_id = 101)  (actual time=0.120..12.35 rows=5000 loops=1)
*/

-- 关键指标分析:
-- actual time: 实际执行时间(15.30ms)
-- rows: 实际处理行数(1500行)
-- loops: 循环次数
2. 性能瓶颈定位

sql

EXPLAIN ANALYZE
SELECT u.username, COUNT(o.order_id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2023-01-01'
GROUP BY u.id
HAVING order_count > 5;

-- 分析输出发现:
-- 1.  users表扫描: 12.5ms (扫描5000行)
-- 2.  orders表关联: 85.3ms (嵌套循环5000次)
-- 3.  分组操作: 45.2ms (临时表处理)

-- 优化方案:
-- • 为users.created_at添加索引
-- • 为orders.user_id添加索引  
-- • 考虑预聚合或缓存策略

六、高级技巧:EXPLAIN优化实战

1. 索引优化策略

sql

-- 检查索引使用效率
EXPLAIN 
SELECT * FROM orders 
WHERE user_id = 1001 
AND order_date BETWEEN '2023-01-01' AND '2023-12-31'
AND status = 'completed';

-- 如果key_len过长,说明索引效率不高
-- 优化:创建更精准的复合索引
CREATE INDEX idx_user_date_status ON orders(user_id, order_date, status);

-- 验证优化效果
EXPLAIN 
SELECT user_id, order_date, status 
FROM orders 
WHERE user_id = 1001 
AND order_date > '2023-01-01';
-- Extra: Using index (覆盖索引)
2. 连接优化策略

sql

-- 分析连接顺序优化
EXPLAIN
SELECT * FROM table1 t1
JOIN table2 t2 ON t1.id = t2.t1_id
JOIN table3 t3 ON t2.id = t3.t2_id
WHERE t1.name = 'test' AND t3.value > 100;

-- 如果连接顺序不合理,使用STRAIGHT_JOIN强制顺序
EXPLAIN
SELECT STRAIGHT_JOIN * FROM table1 t1
JOIN table2 t2 ON t1.id = t2.t1_id
JOIN table3 t3 ON t2.id = t3.t2_id
WHERE t1.name = 'test' AND t3.value > 100;
3. 子查询优化

sql

-- 分析子查询执行计划
EXPLAIN
SELECT * FROM users 
WHERE id IN (
    SELECT user_id FROM orders 
    WHERE amount > 1000 AND status = 'completed'
);

-- 如果子查询性能差,考虑改写为JOIN
EXPLAIN
SELECT DISTINCT u.* 
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.amount > 1000 AND o.status = 'completed';

-- 或者使用EXISTS
EXPLAIN
SELECT * FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o 
    WHERE o.user_id = u.id 
    AND o.amount > 1000 
    AND o.status = 'completed'
);

七、常见EXPLAIN问题与解决方案

1. 性能问题诊断表
EXPLAIN现象 可能问题 解决方案
type=ALL 缺少索引 添加合适的索引
Using filesort 排序字段无索引 添加排序索引或优化查询
Using temporary 需要临时表 优化GROUP BY或DISTINCT
rows值过大 索引选择性差 优化索引或查询条件
key=NULL 未使用索引 检查查询条件或索引定义
2. 索引失效场景分析

sql

-- 1. 函数导致索引失效
EXPLAIN SELECT * FROM users WHERE DATE(created_at) = '2023-01-01';
-- 优化:使用范围查询
EXPLAIN SELECT * FROM users WHERE created_at >= '2023-01-01' AND created_at < '2023-01-02';

-- 2. 隐式类型转换
EXPLAIN SELECT * FROM users WHERE phone = 13800138000; -- phone是varchar
-- 优化:使用正确类型
EXPLAIN SELECT * FROM users WHERE phone = '13800138000';

-- 3. 前导通配符
EXPLAIN SELECT * FROM users WHERE name LIKE '%john%';
-- 优化:使用全文索引或改写查询

八、EXPLAIN最佳实践指南

1. 日常优化流程

sql

-- 步骤1:识别慢查询
SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10;

-- 步骤2:使用EXPLAIN分析
EXPLAIN FORMAT=JSON 
-- 粘贴慢查询SQL here

-- 步骤3:优化索引和查询
-- 根据EXPLAIN结果添加索引、重写查询

-- 步骤4:验证优化效果
EXPLAIN ANALYZE
-- 优化后的SQL here

-- 步骤5:监控持续性能
SHOW STATUS LIKE 'Handler_read%';
2. 自动化优化脚本

sql

-- 自动分析常用查询
SELECT 
    query,
    EXPLAIN_FORMAT = JSON_EXTRACT(EXPLAIN_FORMAT, '$.query_block.cost_info.query_cost') as cost
FROM (
    SELECT 
        sql_text AS query,
        EXPLAIN_FORMAT = (EXPLAIN FORMAT=JSON sql_text)
    FROM performance_schema.events_statements_summary_by_digest
    WHERE digest_text LIKE 'SELECT%'
    ORDER BY sum_timer_wait DESC
    LIMIT 10
) AS top_queries
ORDER BY cost DESC;

九、总结:成为EXPLAIN专家

1. 核心技能掌握
  • 快速解读:10秒内看懂EXPLAIN输出

  • 问题诊断:准确识别性能瓶颈

  • 优化方案:提出有效的优化策略

  • 效果验证:量化优化成果

2. 性能优化等级
等级 能力要求 典型优化效果
初级 看懂type和key字段 2-10倍性能提升
中级 分析Extra和rows字段 10-100倍性能提升
高级 使用JSON格式深度优化 100倍以上性能提升
专家 EXPLAIN ANALYZE实战 系统级性能优化
3. 持续学习建议
  1. 实践为主:每天分析1-2个真实查询

  2. 深度理解:研究MySQL源码执行机制

  3. 工具链完善:结合pt-query-digest等工具

  4. 持续监控:建立性能基线并持续优化

通过本文的深度解析,你现在已经掌握了EXPLAIN这个MySQL优化利器。记住:真正的优化大师不是靠猜测,而是靠数据说话。现在就开始使用EXPLAIN分析你的查询,让性能优化变得科学而高效!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐