PL/SQL 存储过程错误处理里,游标 obj 遍历 Sys_User、再调用 sys_User_API.Modify(param1,param2) 初始化密码,是很多初学者的第一段真实业务代码;我让 Codex 走 TaoToken 逐行解释,Key 和 Base URL 从 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content= 拿。一旦某行 Modify 抛错,外层 loop 直接断掉,后面的用户全没跑到;问题往往只是 begin…exception…end 包在 loop 外还是 loop 内。Codex 只做生成、解释、对照代码,不连接 Oracle,也不代替 SQL*Plus 执行;真正跑存储过程、看 Dbms_Output 输出、把报错贴回对话,仍然在读者自己的数据库会话里完成。下面按原文两个 PL/SQL 例子的顺序,把“为什么会断”“该包哪一层”“怎么让 Codex 帮你查格式”“怎么在本地验证”拆开讲。
1. 游标 obj 遍历 Sys_User 时,为什么一行 Modify 出错就让整段停住
1.1 先复现初学者最常写的无异常版本
很多人的第一版长这样:声明游标 obj,从 Sys_User 取一批待初始化密码的用户,loop fetch,然后直接调用 sys_User_API.Modify(rec_.name, '初始密码')。代码看起来顺,但异常处理完全没写。只要 Modify 内部因为某个用户的数据不满足约束而 raise,控制流就不会回到 fetch 下一行,而是直接跳出 loop,游标后面的用户全部漏掉。SQL*Plus 里看到的就是一个 ORA 错误,前面成功初始化的用户可能已经提交,后面的却还没动,数据状态一半一半。
declare cursor obj is select name from Sys_User where 状态 = '待初始化'; rec_ obj%rowtype; begin open obj; loop fetch obj into rec_; exit when obj%notfound; sys_User_API.Modify(rec_.name, '初始密码'); end loop; close obj; end; /这段代码的问题不在游标,也不在 Modify 的参数个数,而在异常传播路径。PL/SQL 的异常如果没有在当前块被捕获,会一层层往外抛;外层没有 exception 分支时,整个匿名块或存储过程就终止。loop 只是普通控制结构,不会自动“跳过坏行”。所以初学者看到的现象是“只处理了前几个用户”,本质是第一个异常把循环打断了。
1.2 报错后 loop 断掉的真实原因:异常向上抛
把执行顺序画出来更清楚:open obj 之后进入 loop,fetch 拿到 rec_,调用 Modify;如果 Modify 内部 raise,当前块没有 exception,异常向上找。找不到就报错退出,exit when 后面的 close obj 都未必执行。换句话说,异常处理的边界决定了“坏一行”的影响力范围。边界在 loop 外,坏一行影响整个循环;边界在 loop 内,坏一行只影响当前这一轮。原文两个例子之所以把初学者绕晕,就是代码块层级看起来差不多,执行结果却完全不同。
这里还要提醒一句:sys_User_API.Modify 如果是别人写的 API,它内部可能已经 commit,也可能没有。外层加 exception 只能让循环继续,不能自动把 Modify 内部做了一半的事情回滚。要不要 savepoint、要不要 rollback、要不要记录错误用户,是业务规则,不是 PL/SQL 语法能替你决定的。Codex 可以帮你解释这些分支,但最终策略仍要你自己在本地数据库里验证。
1.3 给 Codex 的描述模板:先把问题锁在“包错位置”上
把代码贴给 Codex 之前,描述越具体,它越不会给你泛泛的语法讲义。可以这样写:下面有一段 PL/SQL,游标 obj 遍历 Sys_User,循环调用 sys_User_API.Modify(rec_.name, '初始密码') 初始化密码。现在某一行 Modify 报错后,外层 loop 直接退出,我想知道 begin…exception…end 应该包在 loop 内还是 loop 外,并请逐行对比两种写法。不要连接 Oracle,只生成和解释代码。这样 Codex 会把注意力放在异常边界、fetch 顺序、close obj 位置和 Dbms_Output 输出上。
2. 在 Codex 里走 TaoToken 查 PL/SQL 异常格式:先拿 Key 再配 config.toml
2.1 从官网注册并创建 YOUR_API_KEY
先打开 TaoToken 注册账号,进控制台创建 API Key。Key 不要写进文章、不要贴到公开仓库,本文统一用 YOUR_API_KEY 占位。创建好之后,在模型广场确认你要用的模型 ID;不同模型、不同套餐的可用范围会变,所以别抄别人截图里的 ID,以 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content= 模型广场当时列表为准。TaoToken 在这里负责统一 API 通道、Key 和用量记录,它不连接你的 Oracle,也不会替你执行 SQL*Plus 命令。
2.2 ~/.codex/config.toml 里把 provider 指向 https://taotoken.net/api
Codex 的配置写在 ~/.codex/config.toml。核心是 model、model_provider,以及对应 provider 下的 base_url。Base URL 填 https://taotoken.net/api,末尾不要加 /v1,更不要把官网落地页的 utm_source 拼上去。Key 建议放环境变量,再用 env_key 引用,避免明文散落在配置里。下面是一份可复制的骨架;YOUR_MODEL_ID 换成模型广场里真实可用的 ID。
model = "YOUR_MODEL_ID" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"macOS 或 Linux 终端里可以这样导出 Key:
export TAOTOKEN_API_KEY=YOUR_API_KEYWindows PowerShell 里对应写法:
$env:TAOTOKEN_API_KEY="YOUR_API_KEY"注意不要把 Claude Code 的 ANTHROPIC_BASE_URL、ANTHROPIC_AUTH_TOKEN 那一套套到 Codex 上。Codex 读的是自己的 config.toml 和 provider 配置,混用变量只会让你在排障时多绕一圈。改完配置后重新开一个终端,再启动 Codex。
2.3 401 和 provider 不认时先查哪里
配置完第一次调用,最容易遇到两类问题。第一类是 401,通常不是模型问题,而是环境变量没生效、Key 复制时带了空格、或者终端窗口没有重启。第二类是 Codex 提示 provider 不存在或 base_url 读不到,优先检查 config.toml 的层级:model_provider 是否等于自定义 provider 的名字,[model_providers.taotoken] 这一段有没有拼错,base_url 是否误写成带 /v1 或带 UTM 的地址。真正的 Oracle 报错不在这里,PL/SQL 的 ORA- 错误要在 SQL*Plus 里看,不要指望 Codex 通道替你连库。
3. 把 begin…exception…end 放进 loop 内:Sys_User 初始化密码的对照写法
3.1 错误示范:异常处理包在 loop 外,只会处理一次
先看包在 loop 外的写法。外层 begin 里 open、loop、fetch、Modify,最后在 loop 结束之后放 exception。这样写的效果是:Modify 一旦抛错,控制流立刻离开 loop,跳到 exception,后面的用户不会再被 fetch。即使 exception 里写了 Dbms_Output.put_line,也只是打印一次,循环已经结束。原文里初学者疑惑“为什么只有一个错误提示”,原因就在这里。
declare cursor obj is select name from Sys_User where 状态 = '待初始化'; rec_ obj%rowtype; begin open obj; loop fetch obj into rec_; exit when obj%notfound; sys_User_API.Modify(rec_.name, '初始密码'); end loop; close obj; exception when others then dbms_output.put_line('初始化用户密码出错:' || sqlerrm); end; /这段代码不是“错在 exception 没写”,而是“exception 的位置让 loop 无法继续”。它适合那种“只要有一条失败就整体停止”的业务。如果初始化密码要求每个用户独立处理,一条失败不影响其他用户,就要把异常边界缩小到 Modify 这一行周围。
3.2 正确思路:内层 begin 包住 sys_User_API.Modify,外层 loop 继续
正确写法是在 loop 里面再加一个 begin…exception…end,把 sys_User_API.Modify 单独包起来。这样某一行失败时,异常在内层被捕获,内层打印错误用户和 SQLERRM,然后控制流回到内层 end 后面的位置,也就是继续执行下一次 fetch。外层 loop 不会被异常打断,close obj 也能正常执行。Codex 建议你抄回本地的关键结构就是这段内层 begin、exception when others then、end;,但具体参数和表名要按你的库改。
declare cursor obj is select name from Sys_User where 状态 = '待初始化'; rec_ obj%rowtype; begin open obj; loop fetch obj into rec_; exit when obj%notfound; begin sys_User_API.Modify(rec_.name, '初始密码'); exception when others then dbms_output.put_line('初始化用户密码出错!用户(' || rec_.name || '):' || sqlerrm); end; end loop; close obj; exception when others then dbms_output.put_line('游标遍历外层出错:' || sqlerrm); if obj%isopen then close obj; end if; end; /这里保留外层 exception,是为了处理 open、fetch、close 或其它非 Modify 异常。内层只兜住单行 Modify,外层兜住游标级错误。如果业务要求失败行必须回滚,而成功行必须保留,还要看 sys_User_API.Modify 内部是否提交、是否需要 savepoint。可以先在测试库用少量用户试,把失败用户的 name 和 sqlerrm 写进日志表,再决定批量跑还是跳过。
3.3 让 Codex 逐行解释两张代码的执行顺序
拿到上面两个版本后,可以把它们一起贴给 Codex,并加一句:请按行号说明第一版在 Modify 报错时哪些行不会执行,第二版为什么能继续 fetch,并指出 close obj 分别在哪些路径执行。Codex 走 TaoToken 消耗 Token 完成解释,不需要它连数据库。这样的提问能迫使它对比异常边界,而不是只背 when others 的语法。若你想继续对照其他存储过程错误处理格式,也可以沿用同一模板:贴脱敏代码、说清楚期望行为、要求逐行解释,再把建议抄回本地 SQL*Plus 验证。
3.4 在 SQL*Plus 里自己验证:把 Dbms_Output 打开
验证不要在对话里“模拟执行”,而是在本地 SQLPlus 里打开输出:先执行 set serveroutput on,再运行匿名块。观察三件事:报错用户有没有打印、后面的用户有没有继续处理、成功和失败各有多少条。如果 Dbms_Output 没有输出,先确认 serveroutput 是否为 on,再确认调用方没有把输出缓冲区吞掉。把 SQLPlus 的完整报错、打印结果、以及你用的测试数据贴回 Codex,它才能继续帮你判断是异常边界问题,还是 Modify 内部逻辑问题。生产库不要直接全量跑,先用测试用户或 where 条件限制几条。
4. 排障:Codex 配置、PL/SQL 异常和 SQL*Plus 验证各自的坑
4.1 Codex 侧:Base URL 多了 /v1 或 Key 没加载
Codex 侧最常见的排障点还是配置文件。base_url 只写 https://taotoken.net/api,不要写成 https://taotoken.net/api/v1,也不要把它和官网落地页混在一起。官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content= 是给人打开注册、创建 Key、看模型广场和看用量的;填进工具的接口地址是 https://taotoken.net/api。若 Codex 报 401,先 echo 一下环境变量,确认 TAOTOKEN_API_KEY 有值;若提示模型不存在,回模型广场核对模型 ID,不要自己拼日期后缀。
4.2 PL/SQL 侧:when others 吞错、没有回滚、游标没关
PL/SQL 侧不要把 when others 当成万能胶。它能接住异常让循环继续,但如果你只写 null,错误就被吞掉,后面查数据差异会很痛苦。至少把 rec_.name、sqlerrm、当前时间写进日志表或打印出来。还要考虑 Modify 内部的事务行为:如果它已经提交了部分数据,外层捕获异常并不会自动撤销。游标关闭也要注意,内层异常处理不会关闭 obj,外层 exception 里可以用 if obj%isopen then close obj; end if; 兜底。批量初始化密码前先备份或限制范围,是最便宜的安全措施。
4.3 验证侧:SQL*Plus 里先小批量,再对照 Codex 建议改写
SQLPlus 验证时,先用 where rownum <= 3 或某个测试部门限制数据量,确认内层 begin…exception…end 真的能让 loop 继续。然后把 Codex 建议的 Dbms_Output.put_line('初始化用户密码出错!用户(' || rec_.name || ')'); 抄进内层异常,跑一遍,看输出是否符合预期。若失败用户被跳过但后续用户继续,说明异常边界放对了;若整个块停止,说明 exception 还在 loop 外,或者 Modify 的异常没有被内层捕获。把 SQLPlus 的结果贴回对话,再让 Codex 对照下一段存储过程。
5. 跑通之后别让 Key 和额度卡住:对账与下一步
5.1 去模型对话确认模型 ID 和 Base URL
Codex 配置改完后,不要直接拿一段大 PL/SQL 去试。先在 TaoToken 模型对话 用同一把 Key 发一条短消息,确认模型 ID、Base URL、Key 三者能通。对话页能返回内容,再回到 Codex 里问游标和异常处理;如果对话页都不通,问题就在 Key 或模型 ID,不在 PL/SQL 代码。这样能把“通道问题”和“代码问题”分开,排障快很多。
5.2 去控制台看这次 Codex 调用有没有记上账
模型对话通了以后,再让 Codex 解释那两段游标遍历 Sys_User 的代码。解释完回到 控制台 API Keys 看这次调用有没有记上账,顺便确认 Key 没有泄漏到公开仓库。若你准备长期用 Codex 对照存储过程、批量改写错误处理格式,可以打开 Coding Plan 看套餐是否够用。模型 ID 仍以 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content= 模型广场当时列表为准,不要照搬别人配置。
5.3 下一步:把内层异常模板套到其他存储过程
这次的重点不是让 Codex 替你执行 Oracle,而是让它帮你确认 begin…exception…end 的边界。内层包住单行业务调用,外层包住游标和资源释放,错误用户用 Dbms_Output 或日志表留下痕迹。拿到这个模板后,你可以把其他存储过程里的循环调用逐个替换:先脱敏贴给 Codex,问清楚异常应该放哪一层,再把建议抄回本地 SQL*Plus 跑小批量验证。Key 不够或模型要切换时,回到 TaoToken 创建新的 YOUR_API_KEY,Base URL 继续用 https://taotoken.net/api,不要加 /v1,也不要把官网 UTM 带进配置文件。这样下一段存储过程错误处理格式就能继续对照,不会因为某一行 Modify 报错又把整个 loop 拖停。