SQL 优化系统状态机规范¶
本文档是
docs/state_machine_diagram.md的修订版本。 设计原则:状态机层只保留影响跳转的"二分"维度,其余细分降级为二级标签(不影响状态机控制流,仅用于 prompt followup、DBA CSV、exec_trace 日志)。 代码源:core/llm_opt_v3/v3_nodes.py::node_stop、core/llm_opt_v3/feedback.py、core/llm_opt_v3/v3_state.py、core/llm_opt_v3/v3_langgraph_orchestrator.py、core/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未给出优化SQL、LLM解析失败、优化后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=False 且 stop_reason="" |
False |
"" |
主优化轨道,消耗 max_rounds 预算 |
审查模式 |
上一轮优化触发 review_gate;当前轮 review_mode=True |
True |
"" |
纯审查,不执行 SQL,不占预算(r 保持不变) |
结束 |
stop_reason ≠ "" |
— | 正优化 / 需人工确认 / 达到最大轮次 |
终态,写入 round JSON 并触发索引回滚 |
为什么不加"报错"宏状态: 当前实现中所有 "报错类" 反馈(LLM未给出优化SQL、LLM解析失败、优化后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"] = False 且 state["stop_reason"] == "" |
| 宏状态·审查模式 | state["review_mode"] = True 且 state["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 二级标签层做即可,仍不需要宏状态。