面试必知必会(14):MySQL执行计划与SQL优化

Java面试系列文章

面试必知必会(1):线程状态和创建方式

面试必知必会(2):线程池原理

面试必知必会(3):synchronized底层原理

面试必知必会(4):volatile关键字

面试必知必会(5):CAS与原子类

面试必知必会(6):Lock接口及实现类

面试必知必会(7):多线程AQS

面试必知必会(8):CountDownLatch、CyclicBarrier、Semaphore、Exchanger

面试必知必会(9):ThreadLocal

面试必知必会(10):MySQL存储引擎

面试必知必会(11):MySQL事务特性

面试必知必会(12):MySQL索引

面试必知必会(13):MySQL锁机制

面试必知必会(14):MySQL执行计划与SQL优化


目录

    • [一、MySQL 查询执行流程简述](#一、MySQL 查询执行流程简述)
    • [二、使用 EXPLAIN 分析执行计划](#二、使用 EXPLAIN 分析执行计划)
    • [三、SQL 优化实战技巧](#三、SQL 优化实战技巧)
      • 1、合理使用索引
      • [2、避免 SELECT *](#2、避免 SELECT *)
      • [3、优化 ORDER BY 与 GROUP BY](#3、优化 ORDER BY 与 GROUP BY)
      • [4、分页优化(LIMIT OFFSET)](#4、分页优化(LIMIT OFFSET))
      • [5、JOIN 优化](#5、JOIN 优化)
      • [6、利用覆盖索引(Covering Index)](#6、利用覆盖索引(Covering Index))
      • [7、使用索引条件下推(ICP, Index Condition Pushdown)](#7、使用索引条件下推(ICP, Index Condition Pushdown))
    • 四、常见性能陷阱与排查

一、MySQL 查询执行流程简述

在谈优化之前,先了解 MySQL 如何处理一条 SQL:

  1. 连接器(Connector):建立连接、权限验证
  2. 查询缓存(Query Cache)(MySQL 8.0 已移除):若开启且命中,直接返回结果
  3. 解析器(Parser):语法分析,生成解析树
  4. 预处理器(Preprocessor):检查表/列是否存在,权限校验等
  5. 优化器(Optimizer):核心环节,决定使用哪个索引、表连接顺序等,生成执行计划
  6. 执行器(Executor):调用存储引擎(如 InnoDB)接口,逐行读取数据并返回结果

优化的核心在于第5步------让优化器选择最优的执行路径


二、使用 EXPLAIN 分析执行计划

EXPLAIN 是 MySQL 提供的用于查看 SQL 执行计划的命令。它不真正执行 SQL,而是展示"如果执行,会怎么做"

基本用法:

sql 复制代码
EXPLAIN SELECT * FROM users WHERE age > 25;
-- 或
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE age > 25; -- 更详细(推荐)

EXPLAIN 输出字段详解(以传统格式为例):

字段名 含义
id 查询序列号。相同 id 表示同一查询块;不同 id 表示子查询或 UNION。数值越大优先级越高(先执行)
select_type 查询类型: • SIMPLE:简单查询 • PRIMARY:最外层查询 • SUBQUERY:子查询 • DERIVED:派生表(FROM 子句中的子查询) • UNION:UNION 中的第二个或之后的 SELECT
table 当前操作的表名。可能是实际表,也可能是 <derivedN>(派生表)或 <unionM,N>(UNION 结果)
partitions 匹配的分区(如果使用了分区表)
type 访问类型(最重要!),表示 MySQL 如何查找行: • system / const:常量查询,极快 • eq_ref:主键或唯一索引等值查询(联表查询) • ref:非唯一索引(普通索引)等值查询 • range:索引范围扫描(如 BETWEEN、IN、>) • index:全索引扫描(比全表快) • ALL:全表扫描(最差,应避免)
possible_keys 可能使用的索引列表
key 实际使用的索引。若为 NULL,说明未走索引
key_len 使用的索引长度(字节)。可判断是否使用了联合索引的部分列
ref 与索引比较的列或常量。如 const 表示与常量比较,db.table.column 表示与其他表的列关联
rows 估算需要扫描的行数。数值越小越好。注意:这是估算值,不一定准确
filtered 按表条件过滤后剩余行的百分比(MySQL 5.7+)。例如 10.00 表示 10% 的行满足条件
Extra 额外信息,非常重要: • Using index:覆盖索引(无需回表) • Using where:需要回表后再次过滤 • Using temporary:使用临时表(常见于 GROUP BY、ORDER BY 不一致) • Using filesort:需要额外排序(性能杀手) • Using index condition:索引条件下推(ICP)

三、SQL 优化实战技巧

1、合理使用索引

✅ 建议:

  • WHEREJOIN ONORDER BYGROUP BY 涉及的列上建索引
  • 联合索引遵循最左前缀原则(a, b, c) 可用于 a=?a=? AND b=?,但不能用于 b=?c=?
  • 使用 EXPLAIN 验证索引是否被使用

❌ 避免:

  • 在索引列上使用函数或表达式(如 WHERE YEAR(create_time) = 2023),会导致索引失效
  • 高基数(重复值少)的列建索引效果更好;对性别等低基数列建索引意义不大

示例

sql 复制代码
-- 不推荐
SELECT * FROM orders WHERE DATE(create_time) = '2023-01-01';

-- 推荐(使用范围)
SELECT * FROM orders 
WHERE create_time >= '2023-01-01' 
  AND create_time < '2023-01-02';

2、避免 SELECT *

  • 只查询需要的字段,尤其当表中有大字段(TEXT、BLOB)时
    • 减少网络传输
    • 若查询字段都在索引中,可触发 覆盖索引(Using index),避免回表
sql 复制代码
-- 差
SELECT * FROM users WHERE city = 'Beijing';

-- 好(假设 (city, name) 有联合索引)
SELECT name FROM users WHERE city = 'Beijing'; -- Using index

3、优化 ORDER BY 与 GROUP BY

  • 尽量让 ORDER BY 的字段与索引顺序一致
  • 避免 ORDER BY RAND(),性能极差
  • GROUP BY 默认会排序,若不需要排序,加 ORDER BY NULL
sql 复制代码
-- 可能触发 filesort
SELECT user_id, COUNT(*) FROM orders GROUP BY user_id ORDER BY NULL;

4、分页优化(LIMIT OFFSET)

  • 大数据量下 LIMIT 100000, 10 性能很差,因为要跳过 10 万行
  • 优化方案:使用游标(基于上一页最大 ID)
sql 复制代码
-- 慢
SELECT * FROM messages ORDER BY id LIMIT 100000, 10;

-- 快(假设上一页最大 id 是 100000)
SELECT * FROM messages WHERE id > 100000 ORDER BY id LIMIT 10;

5、JOIN 优化

  • 小表驱动大表(MySQL 通常自动优化,但需确认执行计划)
  • 关联字段必须有索引(尤其是被驱动表的 ON 条件列,只有这样"小表驱动大表" 才有意义)
  • 避免多表 JOIN(超过 3 张表需谨慎)
sql 复制代码
-- 确保 orders.user_id 和 users.id 都有索引
SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.status = 'paid';

6、利用覆盖索引(Covering Index)

  • 如果一个索引包含查询所需的所有字段,MySQL 可直接从索引返回数据,无需回表
sql 复制代码
-- 假设索引 idx_city_name (city, name)
SELECT name FROM users WHERE city = 'Shanghai'; -- Using index

7、使用索引条件下推(ICP, Index Condition Pushdown)

  • 把原本在服务器层的过滤提前到存储引擎层,在索引遍历时提前过滤,减少回表次数
sql 复制代码
-- 联合索引 (name, age, city)
SELECT * FROM user 
WHERE name = '张三' AND age > 20 AND city = '北京';
  • 没有 ICP:先通过 name 找到所有记录,回表后再过滤 agecity
  • 有 ICP:在索引层就过滤 age > 20 和 city = '北京',大幅减少回表
  • 可通过 EXPLAINExtra 字段看到 Using index condition

查看 / 开启 / 关闭 ICP

sql 复制代码
-- 查看 ICP 状态
SHOW VARIABLES LIKE 'optimizer_switch';
-- 结果中如果包含 index_condition_pushdown=on,说明已开启

-- 关闭 ICP
SET optimizer_switch = 'index_condition_pushdown=off';
-- 开启 ICP
SET optimizer_switch = 'index_condition_pushdown=on';

四、常见性能陷阱与排查

问题现象 可能原因 解决方案
Using filesort ORDER BY 无法利用索引 添加合适索引,或调整排序字段顺序
Using temporary GROUP BY / ORDER BY 字段不一致 统一排序字段,或使用临时表优化
type: ALL 全表扫描 检查 WHERE 条件是否有索引
rows 值过大 估算扫描行数多 优化条件、添加索引、分页改写
查询偶尔慢 缓存失效、统计信息不准 ANALYZE TABLE 更新统计信息

相关推荐
华夏之光永存1 小时前
独家:国家级光刻机项目架构师面试对话实录
面试·职场和发展
草莓熊Lotso1 小时前
《告别 “会用不会讲”:C++ string 底层原理拆解 + 手撕实现,面试 / 开发都适用》
开发语言·c++·面试
KNeeg_1 小时前
黑马点评完整代码(RabbitMQ优化)+简历编写+面试重点 ⭐
java·redis·后端·spring·面试·职场和发展·黑马点评
FPGA小迷弟1 小时前
FPGA工程师常见面试问题,有参考答案,必学!!!
fpga开发·面试·职场和发展·verilog·fpga·modelsim
Java后端的Ai之路1 小时前
以为AI开发就是调接口?一场25K的面试让我看到真相,原来真正的技术深度在这!
人工智能·面试·职场和发展·agent·ai应用开发
「已注销」1 小时前
面试分享:二本靠7轮面试成功拿下大厂P6
前端·javascript·面试
萧曵 丶1 小时前
MySQL 高频面试题(由浅到深 完整版,面试必背)
数据库·mysql·面试
醉颜凉1 小时前
超详细图解:HTTPS 中的 SSL/TLS 完整握手过程(面试必背)
面试·https·ssl
会编程的土豆1 小时前
【数据结构与算法】空间复杂度从入门到面试:不仅会算,还要会解释
数据结构·c++·算法·面试·职场和发展