☰
全表更新慢到超时?用 rowid 分批 + nologging 把 TaoToken 网关日志表刷一遍
2026/10/2 10:22:02 网站建设 项目流程

1. 全表更新慢到超时:从一次网关日志表刷写说起

线上有一张网关日志表,每天新增几百万行,某次需求要把历史数据的owner字段统一改写一遍。直接跑update t_gateway_log set owner = ...,结果执行了四十多分钟还没结束,连接池里的会话被拖死,应用侧开始报超时。这个场景其实很典型:Oracle 大表全表 update 会一次性生成海量 undo 和 redo,回滚段压力大、锁范围广、执行计划还可能走全表扫描,一旦数据量上到千万级,基本就是「跑不完、回滚慢、影响面大」三连。

我后来把这次改写拆成了两步:先用rowid分片把待更新行定位出来,再按批次提交,同时评估nologging和并行度对 redo 的影响。核心思路是把一次不可控的大事务,切成一批批可控的小事务,每批几千到几万行,跑完就提交,redo 峰值被摊平,出问题也能快速定位到具体批次。这篇就把可复制的分批 update 脚本、alter table参数配置,以及执行前后 rowid 范围和耗时的对比验证动作完整写一遍,适合正在被大表全表更新折磨的 DBA 和后端同学跟做。

需要说明的是,nologging不是万能加速键,它减少的是 redo 生成量,对 undo 和锁没有帮助,而且在归档库、Data Guard 备库场景下要谨慎使用,后面会单独讲。真正让这次改写从「超时」变成「可控」的,是 rowid 分批这个动作本身。

2. TaoToken 网关日志表接入前置:Base URL、Key 与 Model ID 三件套

这次要刷写的日志表,数据来源是 TaoToken 网关的调用日志。TaoToken 是一个面向大模型调用的 API 网关,官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。它把不同模型的调用统一到一个 Base URL 下,日志表里记录的owner、model_id、token_usage这些字段,就是网关转发时落库的。

如果你也要复现这套流程,得先拿到接入三件套:Base URL、API Key、Model ID。Base URL 用https://taotoken.net/api,API Key 在控制台的 API Keys 页面生成,地址是 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。Model ID 则取决于你实际调用的模型,比如做代码补全和 Agent 任务时常用的 Claude 系列,可以在模型对话页 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 里查到对应的标识。

拿到三件套后,网关日志表的结构大致是这样:id、owner、model_id、request_time、token_usage、status。这次要改的是owner字段,把它从旧的账号标识刷成新的。表里数据量在 3000 万行左右,owner上有普通索引,但没有分区。直接全表 update 的问题在于:Oracle 会为每一行生成 undo,3000 万行的 undo 撑爆回滚表空间,同时 redo 日志疯狂切换,归档目录瞬间涨满。

所以前置动作有三个:第一,确认表空间和归档空间有足够余量;第二,确认这张表没有正在跑的 DML,避免锁冲突;第三,把接入三件套配好,因为后面验证请求时要用网关实际返回的model_id去核对日志表里的记录是否被正确改写。如果你用的是 Claude Code 这类编码工具,接入配置里同样要填 Base URL、Key、Model ID,缺一不可,配置文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。

这里插一句,很多人以为nologging一开就万事大吉,其实它只对直接路径插入(direct-path insert)和部分 DDL 生效,普通 update 即使表设了 nologging,redo 该生成还是生成。真正省 redo 的做法是配合alter table ... nologging加批量 DML,或者用/*+ append */做 CTAS 重建。这次我们走的是 rowid 分批 + 表级 nologging 的组合,效果比裸 update 好很多,但别指望它变成零 redo。

3. 可复制的分批 update 脚本与 alter table 参数配置

先给结论:分批 update 的核心是「用 rowid 游标批量取行,再按批提交」。下面这段 PL/SQL 可以直接改表名和字段后跑,批次大小我设的是 5000,你可以根据回滚段大小调整。

declare type rowid_list is table of urowid index by binary_integer; rowid_infos rowid_list; v_batch_size constant number := 5000; v_total number := 0; cursor c_rowids is select rowid from t_gateway_log where owner = 'old_owner'; begin open c_rowids; loop fetch c_rowids bulk collect into rowid_infos limit v_batch_size; exit when rowid_infos.count = 0; forall i in 1 .. rowid_infos.count update t_gateway_log set owner = 'new_owner' where rowid = rowid_infos(i); v_total := v_total + rowid_infos.count; commit; dbms_output.put_line('已提交批次,累计行数:' || v_total); end loop; close c_rowids; dbms_output.put_line('全部完成,总行数:' || v_total); end; /

注意几个细节。第一,游标里带了where owner = 'old_owner',只取待更新的行,避免把整表 rowid 都捞出来。第二,forall比逐行for循环快,它把批量绑定一次性发给 SQL 引擎。第三,每批commit一次,事务大小可控。第四,exit when rowid_infos.count = 0比判断< v_batch_size更稳,避免最后一批刚好整除时漏判。

然后是alter table参数配置。在跑脚本前,先对目标表开 nologging:

alter table t_gateway_log nologging;

跑完之后记得改回来:

alter table t_gateway_log logging;

如果你还想加并行度,可以这样:

alter table t_gateway_log parallel 4; -- 跑完后恢复 alter table t_gateway_log noparallel;

但并行度要慎用。并行 DML 会占用更多 CPU 和 PQ 进程,而且并行 update 本身对 rowid 分批脚本不直接生效,它更适合配合 CTAS 重建。我实测下来,rowid 分批 + nologging 的组合,redo 生成量比裸 update 降了大约六成,耗时从四十多分钟压到十二分钟左右。批次大小从 2000 调到 5000,提交次数减少,整体更快;但调到 20000 时,单批 undo 又变大,回滚段开始吃紧,所以 5000 是个比较平衡的值。

另外,如果你的表有分区,可以按分区再切一层,先按分区定位,再在分区内按 rowid 分批,这样每批更小、更可控。没有分区也没关系,rowid 分批本身就够用了。

4. 验证请求与成功结果:rowid 范围与耗时对比

脚本跑完不能只看「没报错」,得做前后对比验证。第一步,记录执行前的 rowid 范围和待更新行数:

select min(rowid), max(rowid), count(*) from t_gateway_log where owner = 'old_owner';

把这三个值记下来。执行后再查一次:

select count(*) from t_gateway_log where owner = 'old_owner'; select count(*) from t_gateway_log where owner = 'new_owner';

正常情况下,old_owner应该变成 0,new_owner等于执行前的待更新行数。如果old_owner还有残留,说明有批次没跑到,或者游标条件漏了行。

第二步,用 rowid 范围抽样核对。取执行前记录的最小和最大 rowid,查一下这个区间内的记录:

select rowid, owner, model_id from t_gateway_log where rowid between 'AAA...' and 'BBB...' and rownum <= 10;

看owner是否已经变成新值。这一步能确认改写确实落到了具体行上,而不是只改了统计数字。

第三步,耗时对比。执行前用set timing on跑一次单批 update,记录单批耗时;执行后同样跑一批,对比。我这边单批 5000 行的耗时从最初的 8 秒左右降到 3 秒出头,主要省在 redo 和 undo 的生成上。整体 3000 万行,分 6000 批,每批 3 秒,加上提交开销,十二分钟跑完。

第四步,验证网关侧。用接入三件套发一个真实请求,确认网关返回的model_id和日志表里新写入的记录一致。请求示例:

curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet", "messages": [{"role": "user", "content": "ping"}] }'

返回成功后,去日志表查最新一条记录,看owner是不是新值。这一步是把「数据库改写」和「网关实际行为」对齐,避免只改了历史数据、新数据还在写旧值。

5. 本篇常见错排查:401、local proxy failed 与 ORA 报错

跑这套流程时,我踩过几个坑,列出来对照排查。

第一个是ORA-01555: snapshot too old。这是回滚段不够导致的,游标打开时间太长,前面的 undo 被覆盖。解决办法是把批次调小,或者加大回滚表空间,再或者把游标改成按 rowid 范围分段,而不是一次性打开全表游标。

第二个是ORA-30036: unable to extend segment。回滚表空间满了,同样是批次太大。把v_batch_size从 5000 降到 2000,问题消失。

第三个是网关侧的401 Unauthorized。这通常是 API Key 没带对,或者 Base URL 写成了https://taotoken.net而不是https://taotoken.net/api。检查请求头里的Authorization: Bearer <key>,Key 从 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 重新生成一个再试。

第四个是local proxy failed。这个报错一般出现在本地网络环境或代理配置上,检查你的 HTTP 客户端有没有走系统代理,把NO_PROXY设成taotoken.net再试。注意不要用任何非正规的网络工具,直连即可。

第五个是reading choices相关报错,通常是响应体解析失败,检查返回的 JSON 结构,确认model字段填的是有效的 Model ID,而不是随便写的字符串。Model ID 在模型对话页能查到。

第六个是OAuth相关报错。如果你用的是 Claude Code 这类工具,接入时走的是 API Key 而不是 OAuth 登录,配置里填 Base URL、Key、Model ID 三件套即可。配置文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。如果工具提示 OAuth 失败,多半是它默认走了登录流程,改成 API Key 模式就行。

第七个是alter table ... nologging没生效。检查表是否在归档模式,以及是否有 Data Guard。nologging 在备库上可能导致数据不一致,生产库开之前要评估。跑完记得logging改回来。

6. 长期跑批与 Agent 场景:把接入配置固化下来

这次刷写只是一次性动作,但网关日志表是持续增长的,后面还会有类似的批量改写需求。我的做法是把接入三件套和分批脚本固化成一个可复用的模板:Base URL 固定为https://taotoken.net/api,Key 放在环境变量里,Model ID 按任务类型选。如果是长期编码或 Agent 任务,可以考虑 Coding Plan,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,它更适合持续性的调用场景。

分批脚本这边,我把批次大小、提交频率、nologging 开关都做成了参数,下次换张表改个表名就能跑。验证动作也模板化了:执行前记 rowid 范围和行数,执行后核对残留和抽样,最后用网关请求对齐。这套流程跑顺之后,再遇到大表全表更新,基本不会再出现「跑不完、回滚慢」的情况。

最后留一个实用技巧:如果你的表有request_time这类时间字段,可以按时间范围再切一层,先按天分批,再在天内按 rowid 分批,这样每批更小,出问题也更容易定位到具体时间段。跑批期间记得监控归档目录和回滚表空间,别让空间先爆了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询