让大模型读懂执行计划:SQL 索引建议与重写推荐的自动化实践

让大模型读懂执行计划:SQL 索引建议与重写推荐的自动化实践
让大模型读懂执行计划SQL 索引建议与重写推荐的自动化实践一、慢查询的沉默成本为什么 DBA 永远在救火一条核心报表 SQL 突然从两秒涨到两分钟。值班群里一片寂静因为没人第一时间知道原因。慢查询像慢性病平时不响发作要命。传统依赖 DBA 人工 Analy 执行计划、凭经验加索引人力永远追不上业务迭代的速度。更隐蔽的是温水煮青蛙型劣化。随着数据量增长某张表从百万行膨胀到十亿行原本能凑合的扫描逐渐变成全表遍历。没有自动化的巡检这类问题往往要等业务方投诉才暴露。大模型介入的切入口正是执行计划。它把EXPLAIN输出的算子树、行数估算、扫描类型翻译成人话哪里走了全表扫描、哪处索引失效、哪段可以用覆盖索引替代回表。这把原本只有资深工程师能读的计划变成了可批量处理的信号。值得补充的是执行计划分估算与真实两种。EXPLAIN只给优化器的估算可能因统计信息过期而严重失真EXPLAIN ANALYZE才包含真实执行时间。做优化诊断必须采用后者否则模型会基于错误前提给出建议。二、计划解析与建议生成的闭环采集、诊断、验证整个流程分三步。第一步采集对目标 SQL 跑EXPLAIN ANALYZE拿到真实执行统计。第二步诊断把计划喂给大模型让它定位瓶颈并给出索引与重写建议。第三步验证在影子库执行改写后的 SQL对比耗时确认收益后再回流。下面是闭环数据流flowchart TB Q[慢查询 SQL] -- P[EXPLAIN ANALYZEbr/采集执行计划] P -- D[计划解析br/LLM 瓶颈定位] D -- S[(元数据br/表结构 / 现有索引)] S -- D D -- R[建议生成br/索引 重写] R -- V[影子库验证br/耗时对比] V --|有收益| C[建议入库] V --|无收益| F[反馈拒收] style P fill:#e3f2fd style D fill:#fff3e0 style R fill:#f3e5f5 style V fill:#e8f5e9诊断的可靠性来自上下文。大模型若只看计划、不知表结构容易给出无法落地的索引。因此必须同时注入列类型、基数估算与现有索引建议才具备可执行性。三、生产级实现带超时与影子验证的建议器下面给出建议生成器的骨架包含计划采集、超时控制、建议结构化解析与失败降级import asyncio import json from dataclasses import dataclass from typing import Optional dataclass class SqlAdvice: index_suggestions: list None rewritten_sql: Optional[str] None reason: str confidence: float 0.0 class SqlAdvisor: 基于执行计划的 SQL 优化建议器 def __init__(self, db_exec, llm_chat, metadata_store, timeout: float 15.0, max_retry: int 2): self._exec db_exec self._llm llm_chat self._meta metadata_store self._timeout timeout self._max_retry max_retry async def advise(self, sql: str) - SqlAdvice: plan await self._explain(sql) if plan is None: return SqlAdvice(reason计划采集失败跳过。) schema await self._meta.describe(sql) prompt self._build_prompt(sql, plan, schema) for attempt in range(self._max_retry 1): try: resp await asyncio.wait_for(self._llm.chat(prompt), timeoutself._timeout) return self._parse(resp) except asyncio.TimeoutError: if attempt self._max_retry: return SqlAdvice(reason生成超时建议人工复核。) await asyncio.sleep(1) except Exception as e: print(f建议生成异常: {e}) return SqlAdvice(reason生成失败降级人工。) return SqlAdvice(reason未知错误。) async def _explain(self, sql: str) - Optional[str]: try: return await self._exec(fEXPLAIN ANALYZE {sql}) except Exception as e: print(fEXPLAIN 失败: {e}) return None def _build_prompt(self, sql: str, plan: str, schema: str) - str: return ( 你是 SQL 优化专家。执行计划与表结构如下\n f计划{plan}\n结构{schema}\n原 SQL{sql}\n 输出 JSONindex_suggestions 数组、rewritten_sql、reason、confidence。 ) def _parse(self, resp: str) - SqlAdvice: try: data json.loads(resp) return SqlAdvice( index_suggestionsdata.get(index_suggestions, []), rewritten_sqldata.get(rewritten_sql), reasondata.get(reason, ), confidencefloat(data.get(confidence, 0.0)), ) except (json.JSONDecodeError, ValueError): return SqlAdvice(reason建议解析失败需人工确认。)这段代码的核心在四处工程化计划采集失败即短路、生成超时重试、结果强制 JSON 解析、解析失败降级人工。它保证建议器本身不会因单条坏 SQL 而中断整个巡检任务。四、边界与权衡模型建议必须可证伪大模型给出的索引建议可能重复或冲突。它看不到全局写入成本可能建议一张高频写入表加多个索引反而拖垮写入。因此所有建议都要经影子库验证耗时收益并评估对写入的影响再决定是否采纳。重写 SQL 存在语义偏移风险。模型可能为了更快改变连接顺序或过滤条件导致结果与原始语义不一致。验证不能只看耗时还必须在小样本上比对结果行数是否等价防止优化变味。幻觉同样存在。模型可能引用不存在的列或函数。执行层的语法与对象校验是第一道防线任何无法解析的改写都应被拒收并反馈。适用场景读多写少的分析库、周期性慢查询巡检、新上线 SQL 的预检。禁用场景强一致交易的在线库自动改索引或数据极小、优化收益可忽略的表。这类场景自动化带来的风险大于收益。五、总结大模型把执行计划翻译成可操作的优化建议前提是喂足表结构与现有索引。计划采集、超时控制、影子验证三道防线把AI 建议变成可证伪的工程决策。落地先从慢查询巡检切入所有建议经耗时与结果双重验证后再采纳。写入影响评估则守住全局稳定性的底线。