feat: Phase2影响分析 — EXPLAIN预检+大表检测(简化版)+执行策略+风险评估table_sizes支持 (1141测试)

新增:
- explain_preview.py: EXPLAIN预检服务(MySQL/PG), 影响行数预估, 全表扫描检测
- ticket.executor: 三种执行策略(stop_on_error/continue/transaction)
- risk_evaluator: table_sizes参数, 大表ALTER/UPDATE/DELETE自动升级high
- test_phase2_features.py: 22个测试(EXPLAIN+执行策略+大表检测)

简化:
- 砍掉CTX-001上下文感知规则(复杂二次扫描), 合并大表检测到risk_evaluator
- 审核引擎保持纯静态分析, 不再导入_RULE_REGISTRY做二次扫描

模型:
- Ticket加exec_strategy字段
- TicketSqlItem模型
- schema加sql_type/exec_strategy/risk_level/workflow_id
This commit is contained in:
logan committed 2026-05-25 11:02:51 +08:00
1 parent 7be0bf1a7c
commit f6f9142ce8
7 files changed
+574 -22

No files matched your search

+17 -21
View File
@@ -220,29 +220,13 @@ class TicketExecutor:
# ===== MySQL 执行 =====
async def _execute_mysql(self, ticket: Ticket, instance: Instance) -> ExecutionReport:
"""执行 MySQL 工单。
执行流程:
1. 语法预校验(sqlparse 解析)
2. 建立连接(autocommit=False 显式事务)
3. 执行前快照和数据备份
4. 在事务中执行 SQL
5. 执行后验证
6. 验证通过则提交,失败则回滚
7. 生成报告
Args:
ticket: 工单对象
instance: MySQL 实例连接信息
Returns:
ExecutionReport: 执行报告
"""
"""执行 MySQL 工单。"""
import aiomysql
report = ExecutionReport()
start_time = time.time()
sql_content = ticket.sql_content.strip()
exec_strategy = getattr(ticket, "exec_strategy", "stop_on_error") or "stop_on_error"
# 获取超时配置(从 ticket 或选项中)
custom_timeout = getattr(ticket, 'options', None) or {}
@@ -279,6 +263,7 @@ class TicketExecutor:
total_affected = 0
output_lines: List[str] = []
execution_failed = False
failed_count = 0
statements = self._split_statements(sql_content)
for stmt in statements:
@@ -293,10 +278,15 @@ class TicketExecutor:
total_affected += max(0, affected) if affected is not None else 0
output_lines.append(f"[{stmt[:50]}...] 影响 {affected} 行")
except Exception as stmt_error:
failed_count += 1
execution_failed = True
output_lines.append(f"[{stmt[:50]}...] 执行失败: {stmt_error}")
report.errors.append(f"语句执行失败: {stmt_error}")
break
if exec_strategy == "stop_on_error":
break
# continue: 跳过失败继续执行
# transaction: 任何失败都回滚(默认行为,break 后走 rollback)
report.affected_rows = total_affected
report.raw_output = "\n".join(output_lines)
@@ -311,8 +301,14 @@ class TicketExecutor:
sql_content, report
)
# 事务决策:验证通过且无执行错误则提交,否则回滚
if not execution_failed and report.validation_passed:
# 事务决策:根据执行策略处理
if exec_strategy == "continue" and failed_count > 0 and failed_count < len(statements):
# continue 模式:部分失败也算成功,提交已执行的语句
await conn.commit()
report.status = "partial_success"
report.errors.insert(0, f"部分失败: {failed_count}/{len(statements)} 条语句失败")
logger.warning("工单 %d MySQL 部分执行成功: %d 条失败", ticket.id, failed_count)
elif not execution_failed and report.validation_passed:
await conn.commit()
report.status = "success"
logger.info("工单 %d MySQL 执行成功,事务已提交", ticket.id)