0%

优化器

MySQL 优化器(OPTIMIZER_TRACE):解析查询计划的内部逻辑

MySQL 的查询优化器是决定 SQL 执行效率的核心组件,它负责生成最优执行计划(如选择索引、连接方式等)。通过可插拔的 OPTIMIZER_TRACE 工具,我们可以追踪优化器的内部决策过程,了解其如何解析 SQL、估算行数、选择执行计划,从而针对性地优化 SQL。

OPTIMIZER_TRACE 基础配置

查看优化器状态

默认情况下,OPTIMIZER_TRACE 是关闭的,可通过以下命令查看状态:

1
show variables like 'optimizer_trace';

输出示例:

1
2
3
4
5
+-----------------+-----------------------+
| Variable_name | Value |
+-----------------+-----------------------+
| optimizer_trace | enabled=off,one_line=off |
+-----------------+-----------------------+
  • enabled=off:未开启追踪;enabled=on 表示开启。
  • one_line=off:JSON 结果格式化显示;one_line=on 表示单行紧凑显示。

开启优化器追踪

通过 set session 命令临时开启当前会话的追踪功能(仅对当前会话有效):

1
2
-- 开启追踪,JSON 结果单行显示,包含结束标记
set session optimizer_trace="enabled=on,one_line=on",end_markers_in_json=on;

配置追踪内存大小

优化器追踪结果会占用内存,可通过以下命令查看和调整最大内存限制:

1
2
3
4
5
-- 查看当前限制(默认 16KB)
show variables like 'optimizer_trace_max_mem_size';

-- 调整为 32KB(根据需要设置,过大会占用更多内存)
set session optimizer_trace_max_mem_size = 32768;

查看优化器追踪结果

执行目标 SQL 并获取 trace

执行需要分析的 SQL 后,通过 INFORMATION_SCHEMA.OPTIMIZER_TRACE 表查看追踪结果:

1
2
3
4
5
6
7
-- 示例:执行需要分析的 SQL
select id, sum(cost) from jr_form
where id in (select max(id) from jr_form group by jr_code)
group by id;

-- 查看 trace 结果
select trace from information_schema.OPTIMIZER_TRACE;

注意

  • 必须在同一会话中执行 SQL 和查看 trace(每个 session 仅追踪自己的语句)。
  • 若分步执行(先执行 SQL,再查 trace),可能导致 trace 为空,需同时选中两条语句执行(如 Navicat 中)。

trace 结果结构解析

trace 结果以 JSON 格式展示,包含优化器处理 SQL 的完整步骤,核心分为 steps 数组,每个步骤对应优化器的一个阶段。以下是关键阶段的解析:

阶段 1:join_preparation(查询准备)

该阶段主要对 SQL 进行语法解析、扩展和转换,生成优化器可处理的格式。

示例片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"join_preparation": {
"select#": 1, // 查询编号(主查询为 1,子查询递增)
"steps": [
{
"expanded_query": "/* select#1 */ select `jr_form`.`id` AS `id`, sum(`jr_form`.`cost`) AS `sum(cost)` from `jr_form` where ..."
},
{
"transformation": {
"from": "IN (SELECT)",
"to": "EXISTS (CORRELATED SELECT)", // 将 IN 子查询转换为 EXISTS 关联子查询
"chosen": true
}
}
]
}
}

作用

  • 展开别名、替换子查询(如将 IN (子查询) 转为 EXISTS,优化执行效率)。
  • 生成 “扩展查询”(expanded_query),便于后续优化。
阶段 2:join_optimization(查询优化)

该阶段是优化器的核心,包含条件处理、表依赖分析、行数估算、执行计划选择等关键步骤。

(1)condition_processing(条件处理)

WHEREHAVING 等条件进行转换和简化(如常量传播、等式传递)。

示例片段:

1
2
3
4
5
6
7
8
9
10
11
12
{
"condition_processing": {
"condition": "WHERE",
"original_condition": "<in_optimizer>(`jr_form`.`id`, <exists>(...))", // 原始条件
"steps": [
{
"transformation": "constant_propagation", // 常量传播优化
"resulting_condition": "...", // 转换后的条件
}
]
}
}
(2)table_dependencies(表依赖分析)

分析查询涉及的表及其依赖关系,确认是否存在关联或子查询依赖。

示例片段:

1
2
3
4
5
6
7
8
9
{
"table_dependencies": [
{
"table": "`jr_form`",
"row_may_be_null": false, // 表行不可为 null
"depends_on_map_bits": [] // 无依赖其他表
}
]
}
(3)rows_estimation(行数估算)

优化器估算每个表需要扫描的行数,是选择索引的关键依据(行数越少,成本越低)。

示例片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"rows_estimation": [
{
"table": "`jr_form`",
"range_analysis": {
"table_scan": {
"rows": 184, // 全表扫描估算行数
"cost": 44.9 // 全表扫描成本
},
"potential_range_indexes": [ // 可能使用的索引
{
"index": "PRIMARY", // 主键索引
"usable": true
},
{
"index": "jr_code", // 二级索引
"usable": true
}
]
}
}
]
}

作用

  • 对比全表扫描和索引扫描的成本(cost),选择成本更低的方式。
  • 若索引扫描成本低于全表扫描,优化器会优先选择索引。
(4)considered_execution_plans(执行计划选择)

优化器评估多种可能的执行计划,选择成本最低的方案。

示例片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"considered_execution_plans": [
{
"table": "`jr_form`",
"best_access_path": {
"considered_access_paths": [
{
"access_type": "scan", // 全表扫描
"rows_to_scan": 184,
"cost": 42.8, // 成本
"chosen": true // 被选中的计划
}
]
},
"cost_for_plan": 42.8, // 计划总成本
"chosen": true
}
]
}

作用

  • 展示优化器对不同执行方式(全表扫描、索引扫描等)的成本评估。
  • 最终选择成本最低的计划(chosen: true)。
(5)clause_processing(子句处理)

GROUP BYORDER BY 等子句进行优化,如判断是否可利用索引排序 / 分组。

示例片段:

1
2
3
4
5
6
7
{
"clause_processing": {
"clause": "GROUP BY",
"original_clause": "`jr_form`.`id`",
"resulting_clause_is_simple": true // 简单分组,可优化
}
}
阶段 3:join_explain(执行计划生成)

最终生成 EXPLAIN 语句可展示的执行计划,标志优化过程结束。

OPTIMIZER_TRACE 的实用场景

  1. 分析索引未被使用的原因
    若预期使用索引但实际未使用,通过 rows_estimation 查看优化器估算的索引扫描成本是否高于全表扫描(可能因索引选择性低导致)。
  2. 优化子查询性能
    join_preparation 阶段查看子查询是否被转换(如 INEXISTS),若转换后效率仍低,可手动改写子查询为 JOIN
  3. 理解分组 / 排序的成本
    considered_execution_plans 中查看 sort_cost(排序成本),若成本过高,可通过添加复合索引避免排序(Using filesort)。

注意事项

  1. 性能影响
    开启 OPTIMIZER_TRACE 会增加 MySQL 负担(追踪过程消耗 CPU 和内存),仅在调试时开启,生产环境建议关闭。
  2. 结果大小限制
    若 trace 结果超过 optimizer_trace_max_mem_size,会被截断,需适当调大该参数(但不宜过大)。
  3. 会话隔离
    每个 session 仅能查看自己执行的 SQL 的 trace,无法跨会话查看

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10