跳转至

SQL 优化系统状态机规范

本文档是 docs/state_machine_diagram.md 的修订版本。 设计原则:状态机层只保留影响跳转的"二分"维度,其余细分降级为二级标签(不影响状态机控制流,仅用于 prompt followup、DBA CSV、exec_trace 日志)。 代码源:core/llm_opt_v3/v3_nodes.py::node_stopcore/llm_opt_v3/feedback.pycore/llm_opt_v3/v3_state.pycore/llm_opt_v3/v3_langgraph_orchestrator.pycore/llm_opt_v3/stop_reasons.py


一、状态维度

整个系统的运行时状态 = 下面 5 个维度的组合。

# 维度 字段/来源 取值集合(精简后)
D1 宏状态 review_mode + stop_reason 派生 IDLE / 优化模式 / 审查模式 / 结束
D2 轮次位置 round vs max_rounds 中间轮 (r<max_rounds) / 末轮 (r≥max_rounds)
D3 优化轮产出 feedback.status(执行反馈分支) 正优化 / 优化不成功
D4 审查轮产出 model_review_conclusion(审查反馈分支) 审查通过 / 审查未通过
D5 结束子原因 stop_reason(中文) 正优化 / 需人工确认 / 达到最大轮次 / ""(未结束)

流程假设:审查门禁默认开启。也就是说,D3=正优化 时总是先进入审查模式。

二级标签(不参与状态机跳转,仅影响 prompt followup / CSV 展示 / exec_trace 日志):

  • D3 二级标签:
  • 报错类LLM未给出优化SQLLLM解析失败优化后SQL报错
  • 性能/语义类结果不一致SQL完全相同恶化无变化
  • D4 二级标签:
  • 风险 flag功能风险高,需人工确认(仅在末轮影响终态选择)
  • 其他部分合理无明显优化未明确

二、顶层状态机图

stateDiagram-v2
    [*] --> IDLE
    IDLE --> 优化模式: LoadInputs

    优化模式 --> 优化模式: 本轮 D3=优化不成功 且 r<max_rounds (r+1, 继续优化)
    优化模式 --> 审查模式: 本轮 D3=正优化 (同 r, 不消耗预算)
    优化模式 --> 结束_达到最大轮次: 本轮 D3=优化不成功 且 r≥max_rounds

    审查模式 --> 结束_正优化: D4=审查通过
    审查模式 --> 优化模式: D4=审查未通过 且 r<max_rounds
    审查模式 --> 结束_需人工确认: D4=审查未通过 且 r≥max_rounds 且 风险=功能风险高
    审查模式 --> 结束_达到最大轮次: D4=审查未通过 且 r≥max_rounds 且 风险≠功能风险高

    note right of 结束_达到最大轮次
        recommended_round=0  → 全程无正优化候选
        recommended_round>0  → 有候选但审查未通过,供人工复核
    end note

    结束_正优化 --> [*]
    结束_需人工确认 --> [*]
    结束_达到最大轮次 --> [*]

三、所有合法排列组合(精简后 15 个可观察状态点)

编号约定:S-{宏状态简写}-{序号}。 "下一跳"列指 node_stop 决策后的目标宏状态。 同一行表示一个"系统在该时刻可被观察到的合法状态点"。

3.1 IDLE(1 点)

编号 review_mode stop_reason D3 / D4 下一跳
S-IDLE "" 优化模式·第1轮

3.2 优化模式(4 点 = D3 × 轮次)

编号 D3 轮次位置 下一跳 stop_reason 写入
S-OPT-1 正优化 中间轮 审查模式(同 r, 不消耗预算) ""
S-OPT-2 正优化 末轮 审查模式(同 r, 不消耗预算) ""
S-OPT-3 优化不成功 中间轮 优化模式(r+1) ""
S-OPT-4 优化不成功 末轮 结束·达到最大轮次 达到最大轮次

3.3 审查模式(6 点 = D4 × 轮次 × 风险 flag)

编号 D4 轮次位置 风险 flag 下一跳 stop_reason 写入
S-REV-1 审查通过 中间轮 结束·正优化 正优化
S-REV-2 审查通过 末轮 结束·正优化 正优化
S-REV-3 审查未通过 中间轮 功能风险高 优化模式(r+1) ""
S-REV-4 审查未通过 中间轮 其他 优化模式(r+1) ""
S-REV-5 审查未通过 末轮 功能风险高 结束·需人工确认 需人工确认
S-REV-6 审查未通过 末轮 其他 结束·达到最大轮次 达到最大轮次

3.4 结束模式(4 点)

编号 终态语义 stop_reason recommended_round 由哪些点进入 DBA 操作建议
S-END-OK E1·正优化 正优化 >0(已确认) S-REV-1, S-REV-2 可直接采纳
S-END-MAN E2·需人工确认 需人工确认 >0(未确认) S-REV-5 人工审查后决定是否采纳
S-END-MAX-C E3·达到最大轮次·有候选 达到最大轮次 >0(未确认) S-OPT-4(D3=正优化历史已记录)、S-REV-6 参考第 N 轮候选,人工决定是否采纳
S-END-MAX-N E4·达到最大轮次·无候选 达到最大轮次 0 S-OPT-4(全程 D3=优化不成功) 建议人工改写或放弃

S-END-MAX-C 与 S-END-MAX-N 共用同一个 stop_reason 常量,区分字段是 state["recommended_round"]

3.5 合计

宏状态 状态点数
IDLE 1
优化模式 4
审查模式 6
结束模式 4
总计 15

四、宏状态(D1)

宏状态 进入条件 review_mode stop_reason 备注
IDLE LoadInputs 之前;初始化 round=1、各 last_* "" 概念入口,无显式 LangGraph 节点
优化模式 review_mode=Falsestop_reason="" False "" 主优化轨道,消耗 max_rounds 预算
审查模式 上一轮优化触发 review_gate;当前轮 review_mode=True True "" 纯审查,不执行 SQL,不占预算(r 保持不变)
结束 stop_reason ≠ "" 正优化 / 需人工确认 / 达到最大轮次 终态,写入 round JSON 并触发索引回滚

为什么不加"报错"宏状态: 当前实现中所有 "报错类" 反馈(LLM未给出优化SQLLLM解析失败优化后SQL报错)都走 D3=优化不成功 的通用分支,没有任何代码路径会把"报错"作为独立终态。图外未捕获异常(DB 连不上等)由 run_single_workflow.py:178-185 直接 raise 抛出,是工作流崩溃,不是合法状态。详见附录 §九.2。


五、D3 优化轮产出

build_execution_feedback 计算 feedback.status(互斥,按代码顺序):

D3 取值 状态机意义 触发条件(核心字段) 二级标签
正优化 触发 needs_review_gate=True,进入审查模式 结果一致 + time_saved>阈值 + improvement>阈值 + sql 不相同
优化不成功 走"下一轮 / 末轮达上限"通用分支 其余所有情况 报错类:LLM未给出优化SQL / LLM解析失败 / 优化后SQL报错
性能/语义类:结果不一致 / SQL完全相同 / 恶化 / 无变化

二级标签的判定优先级(来自 feedback.py 的 if/elif 顺序):

优先级 二级标签 触发字段
1 LLM未给出优化SQL optimized_sql 为空
2 LLM解析失败 optimized_sql_complete=False
3 优化后SQL报错 exec_error 非空
4 结果不一致 执行成功但结果与原 SQL 不一致
5 SQL完全相同 normalize_sql(opt)==normalize_sql(org)
6 恶化 结果一致 + time_saved<-阈值improvement<-阈值
7 无变化 结果一致 + 性能未越过双阈值
8 正优化,归 D3=正优化) 结果一致 + 性能越过双阈值 + sql 不同

六、D4 审查轮产出

build_review_feedback 抽取 model_review_conclusion 后写入 feedback.status = f"审查完成({conclusion})"

D4 取值 状态机意义 来源 conclusion 风险 flag
审查通过 直接进入终态·正优化(无论 r 是否到 max_rounds) 正优化
审查未通过 中间轮回优化模式;末轮按风险 flag 选择终态 部分合理 / 无明显优化 / 未明确 / 功能风险高,需人工确认 功能风险高,需人工确认 标为 True,其余 False

风险 flag 只在 D4=审查未通过 + 末轮 时影响跳转:

风险 flag 末轮终态
功能风险高 需人工确认
其他 达到最大轮次

七、结束子状态(D5)

stop_reason 只有 3 个合法取值,但"达到最大轮次"内部按 recommended_round 再细分为两种业务语义,合计 4 个结束子状态

编号 终态语义 stop_reason 常量 recommended_round 进入条件 DBA 操作建议
E1 正优化 effective_positive_optimization >0(已确认) D4=审查通过(任意轮) 可直接采纳
E2 需人工确认 manual_confirmation_required >0(未确认) D4=审查未通过 + 末轮 + 风险=功能风险高 人工审查后决定是否采纳
E3 达到最大轮次·有候选 reach_max_rounds >0(未确认) 末轮 + D3/D4 均未触发 E1/E2 + 曾出现过正优化候选 参考第 N 轮候选,人工决定是否采纳
E4 达到最大轮次·无候选 reach_max_rounds 0 末轮 + D3/D4 均未触发 E1/E2 + 全程无正优化候选 建议人工改写或放弃

E3 / E4 共用同一个 stop_reason 常量,区分依据是 state["recommended_round"]。 代码位置:node_stop::finish() 末尾 if recommended_round > 0 分支(v3_nodes.py 第 552-555 行)。

~~正优化待复核~~ (POSITIVE_REVIEW_GATE_INCOMPLETE):常量已定义但 node_stop 未使用,仅在 STOP_REASON_DISPLAY_STATUS 中作 CSV 展示备用,当前是死代码。


八、转移条件总表(按"目标宏状态"分组)

8.1 → 优化模式

来源 条件 落点
IDLE LoadInputs 完成 优化模式·r=1
优化模式 本轮 D3=优化不成功 且 r<max_rounds 优化模式·r+1
审查模式 D4=审查未通过 且 r<max_rounds(任意风险 flag) 优化模式·r+1

8.2 → 审查模式

来源 条件 落点
优化模式 D3=正优化 审查模式·同 r(审查轮不占预算)

8.3 → 结束模式

来源 条件 终态
优化模式 r≥max_rounds 且 D3=优化不成功 达到最大轮次
审查模式 D4=审查通过(任意轮) 正优化
审查模式 r≥max_rounds 且 D4=审查未通过 且 风险=功能风险高 需人工确认
审查模式 r≥max_rounds 且 D4=审查未通过 且 风险≠功能风险高 达到最大轮次

九、附录

9.1 与代码的字段映射

状态机概念 对应代码字段/常量
宏状态·优化模式 state["review_mode"] = Falsestate["stop_reason"] == ""
宏状态·审查模式 state["review_mode"] = Truestate["stop_reason"] == ""
宏状态·结束 state["stop_reason"] != ""
进审查门禁 默认开启;D3=正优化 时 feedback["needs_review_gate"] == True
审查通过 model_review_conclusion == "正优化"
末轮判定 r >= max_rounds(见 node_stop
审查轮不占预算 node_stop 第 533-535 行:return {"round": r, "stop_reason": ""}
推荐轮 state["recommended_round"](在 _finalize_feedback_state 中当 effective_positive_optimization=True 时记下)

9.2 "为什么不加报错宏状态"细节

所有可能的"报错"来源及当前处理方式:

报错来源 实际处理 是否影响宏状态
LLM未给出优化SQL feedback.status,按 D3=优化不成功 走下一轮 否,落在优化模式
LLM解析失败 同上
优化后SQL报错 同上
索引 DDL 创建失败 _append_exec_trace_event("✗ 索引创建失败") 写日志,主流程不中断
列选择性采集异常 try/except 静默吞掉 + warning
索引回滚失败 _append_exec_trace_exception("索引回滚失败") 写日志
图外未捕获异常(DB 连不上、文件读不到等) run_single_workflow.py:178-185 写 trace 后直接 raise 不写 stop_reason,整个图崩溃,不是合法终态

未来若要新增"报错"宏状态/终态的判据: - 想让"连续 N 轮 SQL 执行报错"立即终止 case → 新增 stop_reason="执行异常" 这种独立终态。 - 想把图外异常变成可观测终态而不是 raise → node_stop 之外加全局 error hook。 - DBA 看板要把"报错收口"和"无收益收口"分开统计 → 只在 D3 二级标签层做即可,仍不需要宏状态。