bigywater.tech
MySQL / InnoDB · 48

OPTIMIZER_TRACE 怎么定位选计划原因

打开OPTIMIZER_TRACE,把上一篇index_merge的代价拆成读索引、回表、去重三块,用真实数字验证'去重才是大头'这个结论。

3657.61
index_merge代价里,真实的去重成本
占比约81%
去重成本在index_merge总代价里的真实比例

做的事:把上一篇的index_merge查询,用OPTIMIZER_TRACE拆开看

EXPLAIN只告诉你最终选了哪个计划,OPTIMIZER_TRACE会把优化器内部比较过的每一个候选方案和它们各自的代价数字都记录下来。

代码SET optimizer_trace='enabled=on'; SELECT COUNT(*) FROM orders WHERE customer_id = 500 OR amount > 475; SELECT TRACE FROM information_schema.OPTIMIZER_TRACE; SET optimizer_trace='enabled=off';

验证:trace里真实拆出了index_merge代价的三个组成部分

实测 · 本机OPTIMIZER_TRACE输出(analyzing_index_merge_union节选)
"indexes_to_merge": [
  { "index_to_merge": "idx_cust",   "cumulated_cost": 0.560549 },
  { "index_to_merge": "idx_amount", "cumulated_cost": 355.08 }
],
"cost_of_reading_ranges": 355.08,        ← 读两条索引range本身的代价
"cost_sort_rowid_and_read_disk": 514.794, ← 按rowid排序+回表读磁盘的代价
"cost_duplicate_removal": 3657.61,        ← 去重的代价,真实最大的一块
"total_cost": 4527.48

对照:table_scan候选方案:
"table_scan": { "rows": 49488, "cost": 5135.15 }

trace里真实把index_merge这一个候选方案的代价拆成了三块:cost_of_reading_ranges(355.08,读idx_cust和idx_amount两条索引range本身)、cost_sort_rowid_and_read_disk(514.79,把两条索引扫描出的rowid排序后回表读数据)、cost_duplicate_removal(3657.61,对读出来的行按主键去重)。三个数字里,去重这一项真实是另外两项加起来的5倍还多——这和上一篇在真实计时里观察到的"index_merge在低选择性下变慢"完全对得上:匹配行数越多,需要去重的行就越多,这一块代价会持续膨胀。

验证:trace里还能看到WHERE条件是怎么被优化器改写的

实测 · 本机OPTIMIZER_TRACE输出(condition_processing节选)
"original_condition": "((customer_id = 500) or (amount > 475))",
"steps": [
  { "transformation": "equality_propagation",
    "resulting_condition": "(multiple equal(500, customer_id) or (amount > 475))" },
  { "transformation": "constant_propagation", ... },
  { "transformation": "trivial_condition_removal",
    "resulting_condition": "(multiple equal(500, customer_id) or (amount > 475.00))" }
]

trace真实记录了WHERE条件从原始文本到最终形态经过的每一步改写:equality_propagation把customer_id = 500规范成内部的"多重相等"表示,constant_propagation和trivial_condition_removal做进一步的常量传播和化简。这类改写平时完全不可见,只有打开OPTIMIZER_TRACE才能真实看到优化器在背后做了什么。

结论:EXPLAIN告诉你选了什么,OPTIMIZER_TRACE告诉你为什么

EXPLAIN(包括EXPLAIN ANALYZE)只会展示优化器最终选中的那一个执行计划,如果想知道"为什么没选另一个看起来也合理的方案",或者想验证一个执行计划贵在哪个具体步骤,就需要OPTIMIZER_TRACE——它把优化器内部比较过的全部候选方案、每个方案的代价拆解、条件改写的每一步都真实记录了下来。代价是trace本身的JSON输出可以非常大(这次简单的查询trace文本就有好几KB),而且optimizer_trace_max_mem_size有默认上限,复杂查询的trace可能被截断,所以一般只在排查具体问题时临时打开,不会在生产环境常开。

分步演示:从OPTIMIZER_TRACE里拆出index_merge的真实成本

每一步都是本文上面用OPTIMIZER_TRACE跑出来的原始结果。

index_merge代价的真实三段拆解
这一步新拆出的代价分量

    本文用到的工具

    • 本机 MySQL 26.7.0(InnoDB),复用上一篇《索引合并为什么有时更慢》的查询和数据
    • SET optimizer_trace='enabled=on' + information_schema.OPTIMIZER_TRACE
    京ICP备2026067502号-1京公网安备11010502063487号