ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

解析Oracle 8i/9i的计划稳定性:用TaoToken统一Key复现Stored Outlines执行计划

解析Oracle 8i/9i的计划稳定性:用TaoToken统一Key复现Stored Outlines执行计划 1. 老库里的执行计划为什么说变就变维护 Oracle 8i/9i 的 DBA 大概都遇到过这种场景一条跑了三年的 SQL某天凌晨统计信息一刷新执行计划从索引扫描变成全表扫描业务响应时间从 200ms 涨到 8s。更麻烦的是这条 SQL 藏在 wrap 过的存储过程里你连源码都看不到改代码这条路直接堵死。这就是 Stored Outlines存储概要要解决的问题。它是 Oracle 8.1 引入的计划稳定性特性核心思路是把某条 SQL 当时正确的执行路径以一组 hint 的形式固化下来存进数据字典。之后只要匹配到同一条 SQLOracle 就绕过优化器直接套用这份固化好的 hint 列表。对老版本数据库来说这几乎是唯一能在不改应用的前提下锁住执行计划的手段。它带来三个实际好处。第一可以优化那些开销极大的语句把一次昂贵的硬解析成本摊薄。第二对于那些优化阶段本身就耗时的语句能省下优化时间、减少 library cache 里的竞争。第三可以放心启用 cursor_sharing 参数不用担心绑定变量窥探导致计划漂移。但 8i/9i 的存储概要有个硬伤8i 要求 SQL 文本完全一致才会匹配多一个空格、大小写不同都不认。9i 才引入标准化处理比对前统一转大写、去空格。这个差异直接决定了你在两个版本上的操作手法不一样。我试过在 9i 上直接照搬 8i 的脚本结果概要死活不生效排查半天才发现是文本匹配规则变了。所以下面我会把两个版本的差异点标清楚你按自己的版本对号入座。这篇面向的是还在维护老库的 DBA所以我不讲理论空话直接给可复制的建表、建过程、创建概要、交换 hint、验证锁定的完整流程。同时我会用 TaoToken 的统一 Key 通道调用模型辅助生成对比 SQL 和排查思路——老库文档难找有个能随时问的助手确实省事。2. 用 TaoToken 统一 Key 打通模型辅助通道老版本 Oracle 的资料散落在各种归档文档里遇到ORA-报错想快速定位靠翻 PDF 效率太低。我的做法是接一个模型通道把报错原文、SQL 片段、执行计划贴进去让它帮我梳理排查方向。TaoToken 在这里的作用是提供一个统一的 API 入口不用为不同模型分别维护 Key。先说清楚它是什么TaoToken 是一个模型 API 聚合通道你用同一个 Key 就能调用多种模型适合需要频繁切换模型做对比验证的场景。对 DBA 来说典型用法是把一段 SQL 和它的执行计划丢给模型让它分析 hint 是否合理、有没有更优的索引组合。适合谁用需要长期做 SQL 调优、又不想在多个模型平台之间来回注册的运维和开发。如果你只是偶尔问一次用网页版就够了但如果你要把模型调用嵌进日常排障流程统一 Key 会省掉很多切换成本。接入前你需要准备三样东西这三件套缺一不可Base URLhttps://taotoken.net/apiAPI Key在控制台创建形如sk-开头的一串字符Model ID按你需要的模型填比如做代码和 SQL 分析时选对应的模型标识获取 Key 的入口在控制台的 API Keys 页面创建后记得立刻复制保存页面刷新后就看不全了。模型对话的调试入口可以用来先验证 Key 是否可用不用写代码就能发一条测试请求。这里有个容易踩的坑Base URL 末尾不要自己加/v1或斜杠不同客户端对路径拼接的处理不一样多加了反而 404。标准写法就是https://taotoken.net/api具体路径由客户端或 SDK 补全。配置好之后你就可以在排障时把 Oracle 的报错、user_outlines查询结果、tkprof输出贴给模型让它帮你判断概要是否被正确应用。下面进入正题先看存储概要的完整创建流程。3. 可复制的 Stored Outlines 创建与固定配置这一节是全文技术核心我给的是能直接跑的脚本。先建环境再创建概要最后交换 hint 锁定计划。3.1 准备用户和测试表创建一个专用用户权限需要create session, create table, create procedure, create any outline, alter session。以该用户连接后执行create table so_demo ( n1 number, n2 number, v1 varchar2(10) ); insert into so_demo values (1,1,One); create index sd_i1 on so_demo(n1); create index sd_i2 on so_demo(n2); analyze table so_demo compute statistics;注意 8i/9i 用的是analyze ... compute statistics不是后来版本的dbms_stats别搞混。3.2 创建被 wrap 的存储过程写一个访问该表的存储过程模拟看不到源码的应用场景create or replace procedure get_value ( i_n1 in number, i_n2 in number, io_v1 out varchar2 ) as begin select v1 into io_v1 from so_demo where n1 i_n1 and n2 i_n2; end; /然后在操作系统层面 wrap 它生成.plb文件wrap inamec_proc.sql响应是Processing c_proc.sql to c_proc.plb。执行.plb建过程后user_source里就查不到 SQL 原文了。这一步是为了还原真实生产环境——你没法通过改代码来加 hint。3.3 捕获当前执行计划并创建概要开一个新 session启动概要收集alter session set create_stored_outlines demo;然后跑一段匿名块触发过程declare m_value varchar2(10); begin get_value(1, 1, m_value); end; /立刻停止收集否则后续 SQL 也会被塞进概要表alter session set create_stored_outlines false;查询生成的概要select name, category, used, sql_text from user_outlines where category DEMO;你会看到类似SYS_OUTLINE_020503165427311的自动命名概要sql_text是SELECT V1 FROM SO_DEMO WHERE N1 :b1 AND N2 :b2。这里就是 8i 的痛点存储文本必须和实际执行文本完全一致才匹配。查看它固化的 hintselect name, stage, hint from user_outline_hints where name SYS_OUTLINE_020503165427311;默认计划里会有FULL(SO_DEMO)也就是全表扫描。假设我们判定AND_EQUAL走两个单列索引更优就显式创建一个带目标 hint 的概要create or replace outline so_fix for category demo on select v1 from so_demo where n1 1 and n2 2;再查user_outline_hintsnameSO_FIX的记录里FULL(SO_DEMO)已被AND_EQUAL(SO_DEMO SD_I1 SD_I2)替换。3.4 交换 hint 锁定计划现在要把好的 hint 换到原来那条 SQL 匹配的概要上。user_outlines和user_outline_hints底层是outln模式下的ol$和ol$hints表直接改update outln.ol$hints set ol_name decode( ol_name, SO_FIX,SYS_OUTLINE_020503165427311, SYS_OUTLINE_020503165427311,SO_FIX ) where ol_name in (SYS_OUTLINE_020503165427311,SO_FIX);还必须同步更新 hint 数量否则概要会损坏导出导入时出问题update outln.ol$ ol1 set hintcount ( select hintcount from ol$ ol2 where ol2.ol_name in (SYS_OUTLINE_020503165427311,SO_FIX) and ol2.ol_name ! ol1.ol_name ) where ol1.ol_name in (SYS_OUTLINE_020503165427311,SO_FIX);完成后新开连接启用概要alter session set use_stored_outline demo;再跑一次过程用sql_trace确认走的是AND_EQUAL路径。3.5 迁移到生产环境开发环境验证通过后重命名并改分类alter outline SYS_OUTLINE_020503165427311 rename to AND_EQUAL_SAMPLE; alter outline AND_EQUAL_SAMPLE change category to PROD_CAT;导出时用参数文件限定范围useridoutln/outln tables(ol$, ol$hints, ol$nodes) fileso.dmp consistenty rowsyes querywhere ol_name AND_EQUAL_SAMPLE注意ol$nodes只有 9i 才有8i 导出时去掉这一项。consistenty很重要保证导出期间数据一致。4. 验证请求与成功结果确认配置完不能只看命令返回成功必须验证计划真的被锁住。我一般分三步走。第一步查概要状态。used列在启用前是UNUSED启用并执行匹配 SQL 后会变成USEDselect name, category, used from user_outlines where category DEMO;如果执行后还是UNUSED说明 SQL 文本没匹配上8i 下大概率是空格或大小写问题。第二步开 trace 抓实际执行路径alter session set sql_trace true; -- 执行存储过程 alter session set sql_trace false;然后用tkprof处理 trace 文件tkprof ora_12345.trc output.txt explainuser/pass在输出里找Rows和Execution Plan部分确认出现AND_EQUAL而不是TABLE ACCESS FULL。这里有个已知现象tkprof输出可能显示两条矛盾路径第一条是概要固化的AND_EQUAL第二条是tkprof自己重新 explain 得到的全表扫描。以第一条为准那才是实际执行的。第三步用 TaoToken 通道做交叉验证。把user_outline_hints的查询结果和tkprof输出贴给模型让它判断 hint 组合是否自洽。请求示例以 curl 为例curl https://taotoken.net/api/chat/completions \ -H Authorization: Bearer sk-你的Key \ -H Content-Type: application/json \ -d { model: 你的ModelID, messages: [ {role: user, content: 以下 Oracle 9i 存储概要的 hint 列表判断 AND_EQUAL 是否会被 FULL 覆盖\nNO_EXPAND\nORDERED\nAND_EQUAL(SO_DEMO SD_I1 SD_I2)\nNOREWRITE} ] }返回里如果模型指出AND_EQUAL与FULL互斥、当前列表无FULL就说明交换成功。这一步不是必须但在 hint 组合复杂时能帮你快速排掉明显矛盾。成功的结果长这样used列变USEDtrace 里执行计划稳定为索引组合业务侧响应时间回到预期区间。三个信号都对上才算真正锁定。5. 本篇常见报错排查老版本环境报错信息不友好我把几个高频问题列出来对照。ORA-01031: insufficient privileges。创建概要时报这个说明用户缺create any outline权限。补授权grant create any outline to your_user;注意这个权限要在概要收集前就授好中途补授对已失败的会话无效得重连。概要创建了但 used 一直是 UNUSED。8i 下九成是 SQL 文本不匹配。检查user_outlines.sql_text和实际执行文本重点看空格、换行、大小写、绑定变量名。9i 有标准化处理会好很多但如果你在 9i 上仍不匹配检查是否用了不同的绑定变量命名。local proxy failed / 连接模型通道失败。用 TaoToken 时如果客户端报代理类错误先确认 Base URL 写的是https://taotoken.net/api没有多余斜杠或/v1。再确认 Key 没有多余空格。如果报 401是 Key 无效或过期去控制台重新创建。reading choices 相关解析错误。这通常出现在客户端把返回体当流式解析但实际是普通 JSON 时。检查请求里是否误加了stream: true老客户端对 SSE 支持不完整先关掉流式。OAuth 或鉴权失败。如果你用的是需要 OAuth 流程的客户端确认 token 刷新逻辑正常。TaoToken 的 API Key 方式是 Bearer 头不需要额外 OAuth 步骤混用会冲突。交换 hint 后概要损坏。多半是漏了hintcount的同步更新。回滚方案是提前用exp备份ol$、ol$hints出问题直接imp恢复。生产环境操作前务必备份。ol$nodes 表不存在。这是 8i 环境该表 9i 才引入。导出参数文件里去掉ol$nodes否则导出直接失败。system 表空间被概要撑满。ol$系列表默认建在 system 表空间概要一多就危险。用exp/imp把它们迁到独立表空间。注意ol$含 long 列迁移时用传统 exp/imp别用数据泵。6. 把模型通道接进日常排障流程存储概要这套东西8i 和 9i 的差异、hint 交换的细节、导出导入的坑光靠记忆很容易出错。我的习惯是把 TaoToken 的模型对话入口常驻在浏览器标签里遇到ORA-报错先贴进去让它给排查方向再去查文档验证。如果你只是偶尔调一次 SQL用模型对话页面就够了不用写代码。如果你要把模型调用嵌进脚本、做批量 SQL 分析那就去控制台建 Key按前面给的三件套配好 Base URL、Key、Model ID。长期做编码和 Agent 类任务的可以看下 Coding Plan额度模型更适合高频调用。接入文档里有各语言 SDK 的完整示例从 curl 到 Python 都有照着改 Base URL 和 Key 就能跑。老库维护本来就费精力能自动化一点是一点。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进