控制台输出优化:日志降噪与执行可追踪性¶
SDD: 审查轮与优化轮解耦¶
背景¶
当前 V3 工作流已经引入 review_mode 和纯审查 prompt:正优化候选出现后,下一轮可以进入 checklist 审查,并在审查轮跳过 SQL 执行。但审查轮和优化轮仍共用部分状态、反馈函数和落盘语义。
问题清单¶
| 编号 | 问题 | 位置 | 类型 |
|---|---|---|---|
| P1 | 审查轮被 max_rounds 挡住,最后一轮出现正优化时无法进入审查 |
core/llm_opt_v3/v3_nodes.py::node_stop |
Bug |
| P2 | 报告 compute_status 对 time_saved 先 round 再判断,和运行时反馈精度不一致 |
core/ana/export_dba_display_csv.py::compute_status |
Bug |
| P3 | 审查结论依赖 review_round_count,下一轮优化时结论会被清空,无法稳定传入 followup prompt |
core/llm_opt_v3/feedback.py / prompting.py |
Bug |
| P4 | build_feedback 同时负责执行反馈和审查结论解析 |
core/llm_opt_v3/feedback.py |
架构 |
| P5 | 图拓扑固定为 Persist -> ExecuteSQL -> Feedback,审查轮靠 node_execute early return 绕过 SQL 执行 |
core/llm_opt_v3/v3_langgraph_orchestrator.py |
架构 |
| P6 | 审查轮和优化轮共用 rN.json 轮次落盘,Report 读取语义混乱 |
node_persist / export_dba_display_csv.py |
架构 |
分阶段方案¶
Step 1: 先修 Bug,不改图架构¶
目标:保持现有 LangGraph 拓扑不变,只修复 P1/P2/P3。
- P1:当
feedback.needs_review_gate == True且尚无审查结论时,下一轮必须进入审查轮,不受max_rounds限制。审查轮不计入优化预算。 - P2:
compute_status中time_saved使用原始浮点差值,不再先round(..., 3)后判断;展示列可以继续保留格式化四舍五入。 - P3:审查结论从
review_round_count中解耦,提升为 state 顶层字段: last_review_conclusionlast_review_summary下一轮优化 prompt 从这两个字段读取上一轮审查反馈。
Step 1 验收:
- 最后一轮正优化仍会进入一次审查。
- 审查不通过后,下一轮优化 prompt 能看到上一轮审查结论和审查摘要。
- 报告状态判定与运行时 build_execution_feedback 对 time_saved 的精度口径一致。
Step 2: 函数级拆分¶
目标:消除 P4。
build_execution_feedback(...):只负责 SQL 执行结果、语义一致性、性能阈值、是否触发 review gate。build_review_feedback(...):只负责从审查输出中提取审查结论和审查摘要。- 图中分别使用
node_execution_feedback/node_review_feedback,不再保留统一node_feedback兼容入口。
Step 3: 图级路由与落盘隔离¶
目标:消除 P5/P6。
目标拓扑:
Persist
├─ review_mode=True -> ReviewFeedback -> StopPolicy
└─ review_mode=False -> ExecuteSQL -> ExecutionFeedback -> StopPolicy
落盘规则:
- 优化轮:继续写 rN.json。
- 审查轮:写 rN_review.json。
- Report 默认跳过 _review.json,只统计优化轮执行结果;审查结果只用于审查备注/人工确认提示。
对话状态规则: - 审查轮使用 reviewer system,只做 checklist 复核。 - 审查不通过后,下一轮优化重新切回 optimizer system,并带上被审查候选、审查结论和摘要。 - 审查反馈只传递给下一轮优化;下一次执行反馈完成后清空,避免后续轮次持续携带旧审查结论。
当前执行状态¶
Step 1/2/3 已完成:Bug 修复、反馈函数拆分、图级路由、审查轮落盘隔离均已落地。
背景¶
DBA 使用 sqlopt-csv 批量优化时,控制台被大量冗余日志淹没,关键信息(优化轮次、状态、时间对比)被淹没在连接/查询细节中。同时,执行验证结果(feedback、时间对比、状态判定)没有持久化到可追踪的日志文件。分步命令(init/collect/run/report)各步骤完成后缺少关键结果反馈,DBA 需要手动打开文件才能确认结果。
现状问题清单¶
P0: 控制台日志冗余¶
问题 1:连接/查询细节刷屏
- client.py 的 __init__、connect()、close()、execute_query() 均为 logger.info
- 一个 3 轮 case 产生 30+ 行 INFO 日志,其中 20+ 行是连接/查询细节
- 涉及文件:core/tdsql_client/client.py(第 66-71、86-92、103、217-226 行)
问题 7:"TDSQL 客户端初始化完成" 完全冗余且误导
- TDSQLClient.__init__ 只是读了 config.ini 配置字典,没有建立任何连接、没有分配任何外部资源
- 但日志写 "客户端初始化完成" 让 DBA 误以为建连成功
- 同一个 case 内被调用 5+ 次(node_load_inputs 2 次 + node_execute 每轮 1 次 + cleanup_case_indexes 1 次)
- 涉及文件:core/tdsql_client/client.py 第 66-71 行
处理方案:
- "客户端初始化完成" → logger.debug
- "成功连接到 TDSQL 数据库" → logger.debug
- "TDSQL 数据库连接已关闭" → logger.debug
- "查询完成" 的 all=[5次明细]、fetch_time、fetch_mode、driver → logger.debug
- "查询完成" 控制台只保留精简版:execute=0.002s 5次=[0.005,0.003,0.002,0.002,0.002] rows=18
- "执行 EXPLAIN 分析" → logger.debug
- sql_exe.py 的 "执行 SQL"、"执行 EXPLAIN"、"✓ SQL 执行完成" → logger.debug
- run_single_workflow.py 的配置四行合并为一行摘要
P0: 索引 DDL 操作控制台可见¶
索引创建/回滚是唯一修改生产库结构的操作,DBA 必须实时可见。
时序约束:
- 索引创建在 node_execute(轮内,验证 SQL 之前)
- 索引回滚在 node_stop → finish() → cleanup_case_indexes(所有轮次结束后)
- 回滚不出现在轮次中间
处理方案:
- sql_exe.py:apply_create_index_ddls:logger.info → print,失败时 print + logger.warning 双写
- sql_exe.py:drop_indexes_by_snapshot_diff:已有 print,保留
- 统一前缀:[索引] 创建: ... / [索引] 删除: ... / [索引] 跳过删除(差集为空)
P0: 关键信息缺失¶
问题 4:看不到优化轮次
- LangGraph 节点函数没有 print 轮次进度,DBA 不知道当前第几轮
- 涉及文件:core/llm_opt_v3/v3_nodes.py
处理方案:
- node_call_llm:print(f" [第{r}轮] 调用 LLM ({model})...")
- node_execution_feedback / node_review_feedback:print(f" [第{r}轮] {status} | 基线={baseline:.3f}s 优化={opt:.3f}s 提升={improvement:.1%}")
P0: stop_reason 中文化¶
"reach_max_rounds"→"达到最大轮次""effective_positive_optimization"→"正优化""manual_confirmation_required"→"需人工确认"- 涉及文件:
core/llm_opt_v3/v3_nodes.py第 1486-1493 行
P1: 分步命令结果反馈¶
sqlopt-init:缺少基线摘要
- 当前只打印文件路径,DBA 看不到执行时间和行数
- 目标:基准: 0.042s | 18行 | EXPLAIN 已生成
sqlopt-collect:统计维度对 DBA 无意义
- 当前按 extractor 粒度统计(6 个 extractor × N 张表),生产库 innodb 无权限时 fail=2N
- DBA 关心的是表级别:核心 4 项(ddl, indexes, partitions, table_stats)齐全 = 表级成功
- innodb 不可用不计入表级失败,单独提示
- 目标:采集完成: 成功=20张, 失败=0张 (innodb 不可用=20张)
sqlopt-run:结果摘要缺轮次概览
- 当前只打印最后一轮状态,没有每轮明细、最优轮次、索引摘要
- 最终收口只分两类:✓ 正优化 和 ✗ 优化失败,失败必须说明具体原因
- 目标格式:
优化结果: nw_0005 | ✗ 优化失败: 无变化
────────────────────────────────────────────
基线: 0.042s
第1轮: 0.041s 无变化
第2轮: 0.042s 无变化
第3轮: 0.041s 无变化
建议: 3轮均无明显提升,SQL 可能已最优或需人工改写
索引: 创建0个 / 回滚0个
────────────────────────────────────────────
优化结果: nw_0001 | ✓ 正优化
────────────────────────────────────────────
基线: 2.341s → 0.892s | 提升=61.9% | 3轮
索引: 创建2个 / 回滚2个
────────────────────────────────────────────
sqlopt-report:只打路径,无摘要 - 目标格式:
报告已生成: display_test_01.csv
────────────────────────────────
正优化: 3 无变化: 5 恶化: 1 报错: 1
总提升: 12.3s 平均提升: 34.2%
────────────────────────────────
跨步骤前置检查
- run_single_workflow.py 自动采集 table_ctx 时打印 [自动] 未发现 table_ctx,正在自动采集...
- sqlopt-report 找不到数据时给操作建议
P1: 执行追踪日志持久化(开发视角)¶
受众:开发人员,排查"程序是否正常跑通"。
设计原则:日志只记录程序执行层面的关键事件和异常,不记录优化结果对比(开发去 data/ 下看落盘文件)。一行 = 一个事件。
目标日志格式(事件流):
2026-05-20 09:27:35 | [nw_0005 第1轮] LLM调用开始 | model=deepseek-v4-pro
2026-05-20 09:27:37 | [nw_0005 第1轮] LLM调用成功 | latency=120.3s | tokens=4521
2026-05-20 09:27:37 | [nw_0005 第1轮] LLM解析成功
2026-05-20 09:27:37 | [nw_0005 第1轮] SQL执行完成 | execute=0.003s | rows=18
2026-05-20 09:27:37 | [nw_0005 第1轮] 反馈计算完成: SQL完全相同
2026-05-20 09:27:37 | [nw_0005 第1轮] ✗ 索引创建失败 | DDL="ALTER TABLE..." | error="Duplicate key name 'idx_xxx'"
2026-05-20 09:29:24 | [nw_0005 第2轮] LLM调用成功 | latency=118.7s | tokens=3890
2026-05-20 09:31:01 | [nw_0005 第3轮] 优化结束 | stop_reason=达到最大轮次
2026-05-20 09:31:02 | [nw_0005] ✗ 异常: 索引回滚失败 | error="Connection refused" | traceback=...
记录的事件类型:
| 事件 | 说明 | 何时写 |
|------|------|--------|
| LLM调用开始 | 模型名、轮次 | node_call_llm |
| LLM调用成功 | 耗时、token 数 | node_call_llm |
| LLM调用失败 | 状态码、错误信息、重试次数 | node_call_llm |
| LLM解析成功/失败 | 解析结果 | node_parse_llm |
| SQL执行完成 | execute 耗时、返回行数 | node_execute |
| SQL执行失败 | 错误信息 | node_execute |
| 索引创建成功/失败 | DDL 文本、错误信息 | apply_create_index_ddls |
| 索引回滚成功/失败 | 删除数、错误信息 | cleanup_case_indexes |
| 反馈计算完成 | 状态 | node_execution_feedback / node_review_feedback |
| 优化结束 | stop_reason | node_stop |
| 异常 | 任何未捕获异常,含 traceback | 全局 |
不记录的内容(开发去 data/ 查看):
- 优化前后时间对比、提升百分比
- 结果是否一致、SQL 是否相同
- status_reason 详细文案
- 索引命中情况
P2: 连接模式(记录,不作为本次修复项)¶
每次 execute_query 都 connect→execute→close,一个 3 轮 case 建连 10+ 次。根因是 LangGraph 节点间无法共享 client 实例。连接池是未来优化方向,本次仅降噪处理。
修改文件清单¶
| 文件 | 修改内容 |
|---|---|
core/tdsql_client/client.py |
日志降级:__init__/connect/close/EXPLAIN → debug;查询结果精简 INFO + 完整 DEBUG |
core/tdsql_client/sql_exe.py |
日志降级:执行 SQL/EXPLAIN/完成 → debug;索引操作改 print |
core/llm_opt_v3/run_single_workflow.py |
配置信息合并为一行;自动采集提示 |
core/llm_opt_v3/v3_nodes.py |
轮次进度 print;stop_reason 中文化;feedback 持久化到 exec_trace 日志 |
core/llm_opt_v3/utils.py |
LLM 调用日志保留 INFO |
core/dataprocess_v3/table_ctx/service.py |
CollectSummary 新增表级别统计字段 |
bin/sqlopt-init |
init 完成后打印基线摘要 |
bin/sqlopt-run |
结果摘要加轮次概览和索引摘要 |
bin/sqlopt-collect |
打印改为表级别统计 |
bin/sqlopt-report |
报告生成后打印状态分布摘要 |
控制台输出目标效果¶
sqlopt-csv(批量)¶
============================================================
处理数据库: tdsql_northwind_local (northwind) | 共 1 条 SQL
============================================================
[Step 1 - Init] 获取基线数据...
[1] Init 完成: northwind_0001 | 基准: 0.042s | 18行
[Step 3 - Run] 执行优化...
[1/1] 优化: northwind_0001 | tdsql_northwind_local | 阈值0.1s/20% | 最多3轮
[第1轮] 调用 LLM (deepseek-v4-pro)...
[索引] 创建: ALTER TABLE t_xxx ADD INDEX idx_xxx (col1)
[第1轮] 无变化 | 基线=0.042s 优化=0.041s 提升=2.4%
[第2轮] 调用 LLM (deepseek-v4-pro)...
[第2轮] 无变化 | 基线=0.042s 优化=0.042s 提升=0.0%
[第3轮] 调用 LLM (deepseek-v4-pro)...
[第3轮] 无变化 | 基线=0.042s 优化=0.041s 提升=2.4%
[索引] 回滚: 删除新增索引 1 个 ✓ t_xxx.idx_xxx
[1/1] ✗ 优化失败: northwind_0001 | 无变化 | 3轮均无明显提升,建议人工改写
[Step 4 - Report] 生成统一报告...
报告已生成: display_test_01.csv
────────────────────────────────
正优化: 0 优化失败: 1 (无变化:1)
────────────────────────────────
============================================================
执行汇总:
总数: 1 ✓ 正优化: 0 ✗ 优化失败: 1 (无变化:1)
============================================================
sqlopt-init(分步)¶
已创建: /Users/.../nw_0005.sql
基准: 0.042s | 18行 | EXPLAIN 已生成
sqlopt-collect(分步)¶
发现 20 张表,开始采集...
采集完成: 成功=20张, 失败=0张 (innodb 不可用=20张)
输出目录: /Users/.../table_ctx
sqlopt-run(分步,单条)¶
优化: nw_0005 | tdsql_northwind_local (northwind) | 阈值0.1s/20% | 最多3轮
[第1轮] 调用 LLM (deepseek-v4-pro)...
[第1轮] ✗ 无变化 | 基线=0.042s 优化=0.041s 提升=2.4%
[第2轮] 调用 LLM (deepseek-v4-pro)...
[第2轮] ✗ 无变化 | 基线=0.042s 优化=0.042s 提升=0.0%
[第3轮] 调用 LLM (deepseek-v4-pro)...
[第3轮] ✗ 无变化 | 基线=0.042s 优化=0.041s 提升=2.4%
[索引] 回滚: 跳过(差集为空)
优化结果: nw_0005 | ✗ 优化失败: 无变化
────────────────────────────────────────────
基线: 0.042s
第1轮: 0.041s 无变化
第2轮: 0.042s 无变化
第3轮: 0.041s 无变化
建议: 3轮均无明显提升,SQL 可能已最优或需人工改写
索引: 创建0个 / 回滚0个
────────────────────────────────────────────
sqlopt-report(分步)¶
报告已生成: display_test_01.csv
────────────────────────────────
正优化: 3 无变化: 5 恶化: 1 报错: 1
总提升: 12.3s 平均提升: 34.2%
────────────────────────────────
P1: sqlopt-report 审查备注(review gate CSV 展示)¶
Review Gate 完整流程¶
触发条件:某轮出现 effective_positive_optimization = True 且 enable_positive_review_gate = True 且 review_round_count == 0(只有正优化才进入,且只触发一次)。
流程:
1. 触发轮(第N轮):node_stop 不停止,继续下一轮,第N轮 JSON 写 stop_reason = ""
2. 审查轮(第N+1轮):LLM 用 checklist 复核,risk_assessment 第一行写 审查结论:正优化/部分合理/无明显优化/功能风险高,需人工确认
3. 审查轮 node_review_feedback 读取 _extract_model_review_conclusion:
- 正优化 → review_allows_positive = True,node_stop 调用 finish("effective_positive_optimization")
- 功能风险高,需人工确认 → finish("manual_confirmation_required")
- 其余 → 带着审查结论和摘要回到下一轮优化
4. stop_reason 只写入最后一轮的 JSON,其余轮 stop_reason = ""
当前问题¶
compute_status 只看执行结果,不看 stop_reason。审查轮的 SQL 执行结果可能仍是 正优化,导致 CSV 里显示 r{n}结果 = 正优化,而实际 stop_reason = manual_confirmation_required(旧版英文原始值)或 需人工确认(新版中文)。DBA 看到 正优化 直接采纳,实际该 case 需要人工确认。
CSV 新增列设计¶
只新增一列"审查备注"(行级,紧跟 sql编号 之后),不改动每轮列结构。
数据来源:遍历该 case 所有轮次 JSON,找到 stop_reason 非空的那一轮(即最终轮)。
| 值 | 触发条件 |
|---|---|
⚠️ 需人工确认 - {审查结论原文} |
最终轮 stop_reason == "需人工确认" 或 "manual_confirmation_required" |
| (空) | 无审查,或审查结论为正优化 |
{审查结论原文}:从最终轮risk_assessment用_extract_model_review_conclusion提取,填入人类可读的结论标签(如"功能风险高,需人工确认")- 不做多级标记(去掉"高风险改写"/"部分合理"中间层),只关注最终需不需要人工介入
汇总统计新增¶
sqlopt-report 终端摘要新增一行(仅在有 case 需人工确认时显示):
⚠️ 需人工确认: 2
修改文件¶
| 文件 | 修改内容 |
|---|---|
core/ana/export_dba_display_csv.py |
build_headers 新增"审查备注"列(header1/header2 各加一格);每轮末列 r{n}优化风险(来自 _review.json);build_row 读最终轮 stop_reason 填充行级"审查备注";不再输出"输出说明"表头行 |
bin/sqlopt-report 或 export_dba_display_csv.py |
终端摘要新增"⚠️ 需人工确认: N" |
目标 CSV 结构(三行表头)¶
行0: 输出参考 | (空列补齐)
行1: (空) | sql编号 | 审查备注 | 基准(合并4列) | 第1轮优化(合并9列) | 第2轮优化 | ...
行2: 输出项 | sqlopt_review_display | 审查备注 | 原sql | 原执行计划 | 原sql时间 | 瓶颈分析 | r1优化后sql | ... | r1优化率 | r1优化风险 | r2...
- 不再输出"输出说明"行;列含义由行2 的
r{n}*列名表达。 - 每轮 9 列:
优化后sql / 新增索引 / 优化后执行计划 / 思考步骤 / 优化策略 / 结果 / 时间 / 优化率 / 优化风险(header2 中带r{n}前缀)。 - 行首多一列行级"审查备注"(仅
stop_reason为需人工确认时填充)。
P0: 批次优化汇总报告(LLM 综合摘要)¶
背景¶
display_{batch}.csv 按轮次平铺展示每个 case 的优化过程,适合逐字段标注。但缺少一份综合性的可读文本:将 N 轮优化数据交给 LLM 做一次总结,输出统一的最终推荐 SQL、整体策略、索引建议和风险评估。
产物¶
summary_{batch}.txt,与 display_{batch}.csv 同目录。
输出格式¶
每个 case 一段,段间用分隔线隔开:
================================================================================
Case: nw_0001
================================================================================
【最终推荐SQL】
SELECT ... ;
【综合思考过程】
(模型综合各轮思考步骤后的统一叙述)
【需要应用的索引】
CREATE INDEX idx_xxx ON table_a(col1);
CREATE INDEX idx_yyy ON table_b(col2);
【整体优化策略】
(模型综合各轮策略后的统一描述)
【优化风险评估】
(模型综合各轮风险 + 审查风险的统一评估)
【性能对比】
基线: 2.341s → 最优轮: 0.892s (第1轮) | 提升=61.9%
最终状态: 正优化
================================================================================
Case: nw_0002
================================================================================
...
数据来源¶
复用 export_dba_display_csv.py 已有的数据读取基础设施:
- discover_families() / choose_family() 定位版本族
- read_json() / read_text() 读取各轮 JSON 和文件
- 不新增任何数据读取逻辑
每个 case 收集的结构化数据:
- 原始 SQL、基线执行时间(raw_data/{folder}/)
- 各轮 opt_res_v3 JSON 中的 thinking_steps、optimization_strategy、create_index_ddl、risk_assessment
- 各轮 mannul_data 中的执行时间、优化后 SQL
- 各轮 compute_status 结果
- 审查轮的 risk_assessment(来自 _review.json)
LLM 调用¶
- 函数:复用
core.llm_opt_v3.utils.call_llm_api_with_messages() - 调用时机:
export_batch_report()完成后,在同一个 Step 4 中调用 - 调用频率:每个 case 一次(非每轮)
- prompt 模板:
core/llm_opt_v3/prompts/summary_v1.txt - 模型:使用同一
model_section="llm"
接入位置¶
| 文件 | 改动 |
|---|---|
core/ana/export_dba_display_csv.py |
新增 _collect_case_rounds()、_build_summary_prompt()、export_batch_summary() 三个函数 |
core/llm_opt_v3/prompts/summary_v1.txt |
新增汇总 prompt 模板 |
core/csv_batch.py |
Step 4 末尾加一行 export_batch_summary() 调用 |
不改动的模块¶
v3_nodes.py、v3_state.py、v3_langgraph_orchestrator.py、run_single_workflow.py— 状态机和节点逻辑不变cli_output.py— 控制台输出不变build_row()/export_batch_report()/build_headers()— CSV 生成逻辑不变
SDD: SQLite 持久化落地(两阶段方案)¶
创建日期:2026-06-24 关联文档:
docs/sqlite_storage_proposal.md(表结构详设计)、core/sqlite_storage/schema.sql(DDL)
背景¶
当前所有生产数据以文件目录承载(raw_data/、opt_res_v3/、mannul_data/、table_ctx/),存在三类痛点:
1. 跨 case 聚合查询慢:ana_multi_version.py 反复扫文件系统、解析 JSON
2. 指纹/分类无法复用:每次都需要重算 dataprocess_v3.cluster_sql / classify_sql
3. 规则资产无沉淀载体:review checklist 只在 prompt 文件里
SQLite 库(core/sqlite_storage/sql_opt_results.db)承担"结构化数据单一源",文件目录在过渡期作为双写备份,最终下线为归档。
阶段划分¶
| 阶段 | 目标 | 范围 | 风险 |
|---|---|---|---|
| 阶段一(P0) | 建库 + 灌存量数据,作为黄金校验集 | 仅新增 core/sqlite_storage/ 模块与 ETL 脚本,生产代码 0 改动 |
低 |
| 阶段二(P1) | 生产代码双写 + 读路径迁移到 DB | 修改 persistence.py / execution.py / sql_exe.py / ana_*.py 等 |
中 |
| 阶段三(P2) | 文件 I/O 下线 | 移除文件读写分支,data/ 转归档 |
低 |
架构决策¶
| 决策 | 选项 | 决议 | 理由 |
|---|---|---|---|
| 本次执行范围 | 仅阶段一 / 一+二 / 先 docs | 仅阶段一 | 先验证数据正确性再改生产路径,降低风险 |
| 阶段二读迁移范围 | 报表优先 / +table_context / 全量 | 全量迁移(含 dataprocess_v3 文件 I/O) |
一次性收尾,避免长期双轨 |
| 阶段二双写失败策略 | warn 不阻断 / fail-fast / +错日志 | warn 不阻断 | 文件仍是 source of truth,过渡期保 workflow |
| 表设计 | 宽表 vs 关联表 | sql_case 保持 37 列宽表(仅删 cluster_size) |
SQLite 分析型场景宽表性能优于 JOIN |
| 枚举字段 | 全 TEXT / 加 CHECK | 16 个固定枚举字段加 CHECK,开放字段保持 TEXT | 利用 SQLite 静态约束,避免脏数据 |
Repository API 设计¶
core/sqlite_storage/ 模块结构:
core/sqlite_storage/
├── schema.sql # 已存在(v2.0,6 表 + 1 视图)
├── connection.py # connect() 上下文 + PRAGMA(WAL/FK/encoding)
├── fingerprints.py # 包装 dataprocess_v3 指纹/分类计算
└── repositories/
├── sql_case_repo.py # upsert / get_by_name / get_by_hash / update_baseline / update_classification
├── opt_result_repo.py # insert_round / list_by_case / latest_by_case
├── opt_exec_repo.py # insert_exec / latest_by_case / list_by_case
├── table_ctx_repo.py # upsert / get_by_table / list_by_db
├── audit_rule_repo.py # seed / list_active / hit
└── rewrite_rule_repo.py # seed / list_active / record_feedback
API 契约:
- 所有写方法支持 upsert 语义(PK 冲突时 UPDATE)
- 所有方法接受 conn 参数(外部管理事务),不内置 commit
- 失败抛 sqlite3.IntegrityError / sqlite3.OperationalError,由调用方决定 warn 还是 fail
- 返回值:upsert 返回 bool(是否新增),list 返回 list[dict],get 返回 dict | None
ETL 流程(阶段一)¶
入口:scripts/migrate_legacy_to_sqlite.py --data-root data/northwind --db core/sqlite_storage/sql_opt_results.db
按 FK 依赖顺序:
| 序 | 表 | 输入路径 | 关键计算 |
|---|---|---|---|
| 1 | table_ctx |
table_ctx/{db.table}/ |
合并 ddl.sql / indexes.json / table_stats.json / partitions.json |
| 2 | sql_case |
raw_data/{folder}/ 三件套 |
调 cluster_sql.normalize_sql_regex 算 sql_digest*;调 classify_sql.classify_sql_by_syntax 算 primary_category*;调 result_compare 算 baseline 3 层 hash |
| 3 | opt_result |
opt_res_v3/{folder}/*.json |
解析 meta.batch_suffix / meta.prompt_version / round_num / review_mode,JSON 字段(thinking_steps/strategy/index_trace 等)保留为字符串 |
| 4 | opt_exec |
mannul_data/{folder}/ 三件套 |
计算 3 层 hash,与 sql_case.baseline_* 对比填 comparison_status |
| 5 | audit_rule |
prompts/review_checklist.txt + 手工 |
抽 review checklist 中明示的规则种子(约 10 条) |
| 6 | rewrite_rule |
手工初版 | 几条样例(谓词下推 / CTE 去重 / EXISTS→JOIN) |
校验(scripts/verify_sqlite_import.py):
- 行数对账:SELECT COUNT(*) 与文件数一致
- 抽样比对:随机 5 个 case,逐字段 diff 文件 vs DB
- 一致性:opt_exec.comparison_status 分布合理(多数 equal)
双写策略(阶段二预案)¶
写路径追加点(不替换原逻辑,仅追加 DB 写):
| 生产代码 | 追加 DB 调用 | 时机 |
|---|---|---|
persistence.py:14 persist_round_result |
opt_result_repo.insert_round() |
JSON 落盘成功后 |
execution.py:11 _write_optimized_sql |
opt_exec_repo.insert_exec() |
三件套落盘后 |
sql_exe.py:213 run_baseline |
sql_case_repo.update_baseline() |
baseline 跑完后 |
csv_batch.py:44 init_raw_dir |
sql_case_repo.upsert_sql_case() |
case 初始化时 |
table_ctx/stores.py:27 write_json |
table_ctx_repo.upsert_table_ctx() |
文件写入后 |
失败处理:所有 DB 写入用 try/except 包裹,logger.warning 记录,不抛异常。文件路径仍是 source of truth。
读路径迁移(阶段二预案)¶
| 生产代码 | 改造方式 |
|---|---|
ana_multi_version.py:46,47 文件发现 |
SELECT DISTINCT sql_case_name FROM opt_result |
ana_multi_version.py:74,101 三件套加载 |
repo 查询替代 |
export_dba_display_csv.py:337,413,472,530 聚合 |
SQL 聚合 + Python 后处理 |
table_context.py:21 build_table_info |
全量迁移(用户决议) |
dataprocess_v3/sql_process/engine/*.py 文件 I/O |
全量迁移(用户决议) |
CLI 开关:bin/sqlopt-run --storage={file,db,both}、bin/sqlopt-report --source={file,db}
验收标准¶
阶段一:
- ✅ core/sqlite_storage/sql_opt_results.db 中 6 表数据齐全(行数对账通过)
- ✅ 生产代码 0 改动(git diff core/ bin/ 为空)
- ✅ 抽样 5 个 case 文件 vs DB 字段一致
阶段二:
- ✅ --source=db 模式下 display.xlsx 与 --source=file byte-level 一致
- ✅ 一轮优化跑完后 sql_case / opt_result / opt_exec 都有正确数据
- ✅ 文件模式行为不变
阶段三:
- ✅ 移除文件 I/O 后所有功能正常
- ✅ data/ 转为归档(按月 zip)