MySql 查询优化器(Optimizer)解析
作者:猩火燎猿
一、查询优化器(Optimizer)简介
查询优化器是数据库内核中最复杂、最核心的模块之一。它的作用是根据解析器和预处理器生成的语法树,结合表的统计信息、索引、SQL语句结构等,生成一套最优的执行计划,以尽可能高效地完成查询。
查询优化器的主要目标是:用最少的资源、最快的速度获取正确的结果。
二、查询优化器的主要任务
1. 逻辑优化
作用
- 对SQL语句的结构做变换和简化,减少不必要的计算。
过程
- 谓词简化:如将
WHERE age > 25 AND age > 20简化为WHERE age > 25。 - 表达式重写:如将
WHERE NOT (a = b)改写为WHERE a <> b。 - 子查询改写:如将某些相关子查询改写为JOIN,提高效率。
- 等价变换:如分布律、交换律等,将SQL语句转换为等价但更高效的形式。
2. 物理优化
作用
- 决定具体的物理操作方式和顺序,选择最优的执行路径。
过程
- 访问路径选择:决定是否走索引,选择哪个索引。
- 连接顺序优化:多表JOIN时,决定各表的访问顺序(如驱动表、被驱动表)。
- 连接算法选择:如嵌套循环连接(Nested Loop Join)、Block Nested Loop Join等。
- 索引覆盖:如果查询字段都在索引中,则只扫描索引而不访问数据页。
- 谓词下推:将过滤条件尽量提前到数据读取阶段,减少数据量。
- LIMIT优化:如只扫描满足LIMIT条件的最少数据。
3. 执行计划生成
作用
- 根据逻辑和物理优化结果,生成一份详细的执行计划(Execution Plan)。
过程
- 执行计划描述了每一步的操作方式、访问对象、使用的索引、连接方式、过滤条件等。
- 可以用
EXPLAIN命令查看执行计划。
三、查询优化器的关键技术
1. 统计信息
- 优化器依赖表的统计信息(如行数、索引分布、字段基数等)来估算不同执行路径的成本。
- 统计信息由存储引擎维护,可通过
ANALYZE TABLE命令刷新。
2. 成本模型(Cost Model)
- 优化器会为每种可能的执行计划估算“成本”,包括CPU、IO、内存消耗等。
- 选择成本最低的执行计划。
3. 索引选择
- 优化器会分析WHERE条件、ORDER BY、GROUP BY等子句,判断哪些索引可以使用。
- 优先选择“选择性高”的索引(即过滤能力强)。
4. 连接顺序优化
- 多表JOIN时,优化器会尝试多种连接顺序,选择成本最低的方案。
- 例如:A JOIN B JOIN C,可能尝试(A→B→C)、(B→C→A)等不同顺序。
5. 子查询与视图优化
- 优化器会尝试将相关子查询转换为JOIN,或将视图进行“内联”展开。
四、优化器的执行流程
- 接收语法树
- 收集统计信息
- 枚举所有可能的执行计划
- 计算每种计划的成本
- 选择成本最低的计划
- 输出最终执行计划
五、EXPLAIN命令与执行计划
通过 EXPLAIN 可以查看优化器生成的执行计划。例如:
EXPLAIN SELECT name FROM users WHERE age > 25 ORDER BY name LIMIT 10;
输出(示例):
| id | select_type | table | type | possible_keys | key | rows | Extra |
|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | users | range | age_idx | age_idx | 500 | Using where; Using index; Using filesort |
含义:
type:访问类型(如 ALL、index、range、ref、const、eq_ref)。key:使用的索引。rows:优化器估算需扫描的行数。Extra:额外信息(如是否排序、是否用索引覆盖)。
六、常见优化器相关问题
- 索引未被使用:可能SQL写法不合理,或统计信息不准确。
- JOIN顺序不优:可以用 STRAIGHT_JOIN 强制顺序。
- 子查询未被优化:可尝试改写为JOIN。
- 执行计划不理想:可通过分析EXPLAIN结果,调整SQL或表结构。
七、优化器的局限与补充
- MySQL优化器并非“全知全能”,有时因统计信息不准或算法限制,选择的计划并非最优。
- 可以通过SQL重写、添加/调整索引、更新统计信息来“引导”优化器。
- MySQL支持部分“优化器提示”(Optimizer Hint),如
USE INDEX、FORCE INDEX、STRAIGHT_JOIN等。
八、流程图
解析树
↓
收集统计信息
↓
枚举执行计划
↓
计算成本
↓
选择最优计划
↓
输出执行计划
九. 优化器的内部算法与决策流程
1 枚举所有可能的执行计划
优化器会根据 SQL 的结构,尝试多种执行路径。例如三表 JOIN,理论上有 6 种连接顺序,优化器会枚举这些顺序,结合不同的索引选择和连接算法,形成多种候选执行计划。
2 计算成本
每个执行计划都会被打分。成本模型会估算:
- IO成本:需要读取多少数据页
- CPU成本:需要进行多少计算、比较、排序
- 内存成本:排序、临时表等需要多少内存
- 网络成本:分布式场景下考虑网络传输
优化器会选出成本最低的执行计划。
3 选择连接算法
常见的连接算法有:
- Nested Loop Join(嵌套循环连接):最常用,适合小表驱动大表或有索引的连接
- Block Nested Loop Join:批量处理一组数据,减少IO
- Hash Join:MySQL暂不支持,但部分商业数据库支持
- Sort-Merge Join:适合大表,先排序后合并
4 谓词下推与索引覆盖
谓词下推(Predicate Pushdown)是指将 WHERE 条件尽量提前到最底层的数据访问阶段,减少无效数据传递。
索引覆盖(Covering Index)指查询所需字段全部在索引中,无需访问数据页,极大提升性能。
十. 常见优化场景与实例
1 多表 JOIN 顺序优化
SELECT * FROM a JOIN b ON a.id = b.a_id JOIN c ON b.id = c.b_id WHERE a.status = 1;
- 优化器会根据 a 表的 status 过滤条件,优先驱动 a 表(小表/过滤性强的表)。
- 可能的执行计划:
- a → b → c
- b → a → c
- c → b → a
- 优化器会估算每种方案的成本,选最优。
2 子查询改写为 JOIN
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 100);
优化器会尝试将 IN 子查询改写为半连接或 JOIN,提高效率。
3 索引选择
SELECT * FROM users WHERE age > 30 AND city = 'Beijing';
- 若 age 和 city 都有索引,优化器会根据选择性(过滤效果)决定用哪个索引。
- 也可能选择联合索引。
十一. 优化器提示(Optimizer Hint)
有时优化器选择的执行计划并非我们期望的,可以用优化器提示强制指定计划:
- USE INDEX:建议优化器使用某个索引
- FORCE INDEX:强制优化器使用某个索引
- IGNORE INDEX:忽略某个索引
- STRAIGHT_JOIN:强制 JOIN 顺序
示例:
SELECT * FROM users USE INDEX (idx_age) WHERE age > 30; SELECT * FROM a STRAIGHT_JOIN b ON a.id = b.a_id;
十二. EXPLAIN 结果详细解读
EXPLAIN 是诊断 SQL 性能的核心工具。常见字段说明如下:
| 字段 | 说明 |
|---|---|
| id | 查询的标识符,复杂查询会有多个 id |
| select_type | 查询类型(SIMPLE、PRIMARY、SUBQUERY、DERIVED 等) |
| table | 当前访问的表名 |
| type | 连接类型(ALL、index、range、ref、eq_ref、const、system) |
| possible_keys | 可能用到的索引 |
| key | 实际用到的索引 |
| key_len | 索引长度 |
| ref | 连接条件引用的字段 |
| rows | 估算扫描行数 |
| filtered | 过滤后剩余行比例(MySQL 5.7+) |
| Extra | 额外信息(Using index, Using where, Using filesort, etc.) |
示例解读:
- type=ALL:全表扫描,性能最差
- type=range:索引范围扫描
- type=ref/eq_ref:索引等值查询,性能较好
- Extra=Using index:索引覆盖,无需回表
- Extra=Using filesort:需要额外排序,可能影响性能
十三. 与其他数据库优化器的对比
- MySQL:主要采用基于成本的优化器,支持多种连接算法和索引优化。
- Oracle/PostgreSQL:优化器更复杂,支持 Hash Join、Merge Join、并行执行等高级特性。
- SQL Server:优化器也非常强大,支持更多执行计划缓存和并行优化。
MySQL 优化器更适合中小型业务,大型复杂查询可能需要手动干预或SQL改写。
十四. 执行计划的生成与结构
优化器最终会输出一份执行计划,描述 SQL 查询的每一步操作。执行计划包括:
- 访问哪些表,顺序如何;
- 使用哪些索引;
- 采用什么连接算法;
- 何时做排序、分组、聚合;
- 是否需要临时表、文件排序等。
执行计划的结构通常是一个树状结构,每个节点代表一个操作(如表扫描、索引查找、JOIN、排序等),节点之间有父子关系,表示数据流动的顺序。
示例
假设有如下 SQL:
SELECT u.name, o.amount FROM users u JOIN orders o ON u.id = o.user_id WHERE u.age > 30 AND o.status = 'paid' ORDER BY o.amount DESC LIMIT 5;
优化器会考虑:
- 先扫描 users 表,利用 age 索引过滤;
- 以 users 为驱动表,关联 orders 表(orders 的 user_id 有索引);
- 对结果按 amount 排序,取前 5 条。
最终执行计划大致为:
TableScan(users) → Filter(age > 30) → NestedLoopJoin(orders, user_id) → Filter(status='paid') → Sort(amount DESC) → Limit(5)
十五. EXPLAIN 实践与分析
基本用法
EXPLAIN SELECT ...;
典型输出解读
| id | select_type | table | type | key | rows | Extra |
|---|---|---|---|---|---|---|
| 1 | SIMPLE | users | range | age_idx | 200 | Using where |
| 1 | SIMPLE | orders | ref | user_idx | 500 | Using where; Using index; Using filesort |
- type=range:users 表用 age 索引做范围扫描,性能较好;
- type=ref:orders 表用 user_id 索引做等值连接;
- Using filesort:需要额外排序,可能影响性能;
- rows:优化器估算的需扫描行数,实际越少越好。
进阶技巧
- 使用
EXPLAIN ANALYZE(MySQL 8.0.18+)可获得真实执行时间与行数,帮助识别瓶颈。 - 结合
SHOW WARNINGS查看优化器为何没用索引。
十六. 优化器的局限与手动优化
局限性
- 统计信息不准确:如果表/索引统计信息过时,优化器可能选错执行路径。
- 索引选择不理想:有时优化器未选用最优索引,需手动干预。
- 复杂 SQL 难以优化:如大量嵌套子查询、复杂表达式,优化器可能“放弃”优化。
- 文件排序/临时表:ORDER BY、GROUP BY 涉及大数据量时,容易使用文件排序或临时表,影响性能。
手动优化手段
- 更新统计信息:
ANALYZE TABLE,让优化器有最新数据。 - 优化器提示:用
USE INDEX、FORCE INDEX、STRAIGHT_JOIN强制优化器按指定方式执行。 - SQL改写:将子查询改为 JOIN,减少不必要的计算。
- 索引设计:为常用过滤条件、排序字段建立复合索引。
- 分解复杂查询:将复杂 SQL 拆分为多步处理,减少优化器压力。
示例
SELECT /*+ USE_INDEX(users, age_idx) */ name FROM users WHERE age > 30;
十七. 优化器与存储引擎协作
- 优化器负责策略,存储引擎负责执行。优化器只决定“怎么做”,具体的数据访问、索引遍历、锁管理等由存储引擎(如 InnoDB)完成。
- 优化器会向存储引擎请求统计信息,决定索引选择、访问顺序。
- 存储引擎可以影响优化器的能力(如 InnoDB 支持行锁、MVCC,有些优化策略才可用)。
十八. 性能调优建议
- 定期用 EXPLAIN 检查慢查询;
- 保持统计信息新鲜;
- 合理设计索引,避免冗余、重复;
- 避免复杂嵌套查询,能用 JOIN 就不用子查询;
- 用 LIMIT 限制返回数据量,减少内存压力;
- 对大表的 ORDER BY、GROUP BY 尽量用索引覆盖。
十九. 参考工具
- EXPLAIN / EXPLAIN ANALYZE:分析执行计划和实际消耗
- SHOW INDEX FROM 表名:查看索引情况
- SHOW PROFILE:分析 SQL 各阶段消耗
- 慢查询日志:定位性能瓶颈
二十、总结
查询优化器决定了SQL的执行效率,是数据库性能的核心。理解优化器的工作原理,有助于编写高效SQL、设计合理索引、诊断性能瓶颈。
- 查询优化器是 SQL 性能的核心,决定了查询的执行效率。
- 了解优化器的决策逻辑,有助于编写高效 SQL、设计合理索引。
- 善用 EXPLAIN 和优化器提示,可以诊断和优化慢查询。
- 对于复杂业务,建议定期分析统计信息、合理设计表结构和索引。
到此这篇关于MySql 查询优化器(Optimizer)详解的文章就介绍到这了,更多相关mysql查询优化器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
