PLSQL 实战题目一:用 dblink + procedure + cursor + forall 写一份可复制的批量同步骨架
发布时间:2026/9/30 21:19:37来源:尧图网络
1. 从一次 900 万行跨库同步说起dblink procedure cursor forall 到底解决什么问题如果你手上有一张远程库的user_login表数据量在千万级要求把它整表搬到本地库还要能重复执行、能看日志、出错能回滚——这就是 PLSQL 实战题目一最典型的场景。核心检索词先摆出来PLSQL 用 dblink 跨库批量同步数据它指的是在 Oracle 里通过 database link 读取远端表再用存储过程封装游标遍历和FORALL批量写入把「取数—转换—落库」整条链路做成一个可重复调用的骨架。它能做什么一句话把原来要写 Java 定时任务、或者手工insert into ... select ...卡死 undo 的活收敛成一个存储过程按批提交、按批记录日志。适合谁适合正在做数据迁移、报表汇总、异构库同步的 DBA 和 PLSQL 开发尤其是被「一次 insert 900 万行把临时表空间打爆」坑过的人。我先把这道题拆成两个子任务后面所有代码都围绕它们展开子任务 A通过dblink把远程user_login迁移到本地字段是user_id / login / login_time。子任务 B把user_login按「用户 小时」汇总写入fact_login_cn(statedate, login_cn, userid)。两个任务的技术骨架完全一致cursor定义结果集 →bulk collect ... limit分批取 →forall批量插 → 循环退出条件 → 异常兜底。区别只在字段类型和汇总 SQL。下面从环境准备开始一步步跑通。2. TaoToken 前置准备把模型对话和 API Key 配好再动手写 PLSQL写 PLSQL 的过程中你会反复遇到「这段游标为什么只取到 5000 行」「forall报 ORA-06550 怎么读」这类问题与其翻文档不如先把一个能随时问的模型对话入口配好。我习惯在动手前把工具链准备好这样排错时不用来回切窗口。第一步打开模型对话页面地址是https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite在这里可以直接贴报错信息、贴游标代码让它帮你逐行解释。比如你把exit when cur_login%notfound or cur_login%notfound is null;这行贴进去它会告诉你%notfound在bulk collect场景下的真实语义——这正是本题最容易写错的地方。第二步如果你打算把「生成建表语句 / 生成存储过程骨架 / 解释执行计划」做成脚本自动化就需要 API Key。进入控制台创建https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite然后在 API Keys 页面生成密钥https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite拿到 Key 之后接口基地址用这个注意 API 地址不带 UTM 参数https://taotoken.net/api第三步如果你是用 Claude Code 这类编码工具来辅助写 PLSQL可以走 Coding Plan把长期编码任务挂上去https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite接入文档在这里里面有 Base URL、Key、Model ID 三件套的完整说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite这里要强调一个原则任何接入都必须写全三件套——Base URL、API Key、Model ID缺一个都会在请求时报 401 或 model not found。下面给一份可直接复制的配置片段以常见的settings.json形式为例路径按你本地实际工具调整{ base_url: https://taotoken.net/api, api_key: sk-你的密钥, model_id: claude-sonnet-4-20250514, timeout: 60 }如果你用的是 TOML 风格的配置等价写法[provider] base_url https://taotoken.net/api api_key sk-你的密钥 model_id claude-sonnet-4-20250514配好之后你写 PLSQL 遇到ORA-报错可以直接把错误码和上下文丢给模型对话让它给出排查方向。这一步不是必须但能省掉大量翻 MOS 文档的时间。工具准备好接下来进入正题建表、建链、写过程。3. 可复制配置建表、建 dblink、写 procedure 骨架这一节是全文的技术核心所有代码都可以直接复制到你的测试库跑。我按「建表 → 建 dblink → 写过程 A → 写过程 B」的顺序给。3.1 建本地目标表和日志表先建两张表迁移目标表user_login和日志表t_log。日志表用来记录每次同步的批次和异常信息。-- 迁移目标表 create table user_login ( user_id number, login number, login_time date ); -- 日志表 create table t_log ( seqid number, msg varchar2(2000), createtime date ); -- 序列供日志表主键使用 create sequence seq_log start with 1 increment by 1;再建汇总结果表对应子任务 Bcreate table fact_login_cn ( statedate number, login_cn int, userid number );3.2 建 dblinkdblink 的名字按题目用px_dblink指向远程库。你需要替换成自己的远程库连接串create database link px_dblink connect to remote_user identified by remote_pwd using (DESCRIPTION (ADDRESS (PROTOCOL TCP)(HOST 10.0.0.20)(PORT 1521)) (CONNECT_DATA (SERVICE_NAME orcl)) );建完先验证链路通不通这一步别跳过select count(1) from user_loginpx_dblink;如果这里就报ORA-12154或ORA-12541说明连接串或监听有问题先解决链路再写过程否则后面所有报错都会混在一起。3.3 过程 Adblink 取数 cursor forall 批量写入这是题目一的核心骨架。注意几个关键点用bulk collect ... limit 5000分批取用forall批量插循环退出条件要同时判断%notfound异常块里回滚并写日志。create or replace procedure p_login as cursor cur_login is select user_id, login, login_time from user_loginpx_dblink; type type_user_id is table of number; v_user_id type_user_id; type type_login is table of number; v_login type_login; type type_login_time is table of date; v_login_time type_login_time; lv_errinfo varchar2(2000); begin open cur_login; loop fetch cur_login bulk collect into v_user_id, v_login, v_login_time limit 5000; forall i in 1 .. v_user_id.count insert into user_login values (v_user_id(i), v_login(i), v_login_time(i)); commit; insert into t_log(seqid, msg, createtime) values (seq_log.nextval, batch inserted: || v_user_id.count, sysdate); commit; exit when cur_login%notfound; end loop; close cur_login; exception when others then rollback; lv_errinfo : 错误信息: || SQLERRM; insert into t_log(seqid, msg, createtime) values (seq_log.nextval, lv_errinfo, sysdate); commit; end p_login;这里有个细节值得单独说原题里写的是exit when cur_login%notfound or cur_login%notfound is null;实际上%notfound是布尔值不会为 nullor ... is null是冗余的。但更重要的是——bulk collect取完最后一批后%notfound才为 true所以退出判断放在forall和commit之后是对的能保证最后一批数据被写入。如果你把exit when放到fetch之后、forall之前最后一批就会丢。3.4 过程 B按小时汇总 forall 写入子任务 B 的骨架和 A 几乎一样只是游标 SQL 换成了group by字段类型变成number / int / number。create or replace procedure p_fact as cursor cur_fact is select to_char(login_time, yyyymmddhh24) as statedate, count(1) as login_cn, user_id from user_login group by user_id, to_char(login_time, yyyymmddhh24); type type_statedate is table of number; v_statedate type_statedate; type type_login_cn is table of int; v_login_cn type_login_cn; type type_userid is table of number; v_userid type_userid; begin open cur_fact; loop fetch cur_fact bulk collect into v_statedate, v_login_cn, v_userid limit 5000; forall i in 1 .. v_statedate.count insert into fact_login_cn values (v_statedate(i), v_login_cn(i), v_userid(i)); commit; exit when cur_fact%notfound; end loop; close cur_fact; end p_fact;注意to_char(login_time, yyyymmddhh24)返回的是字符串而fact_login_cn.statedate是numberOracle 会做隐式转换。如果你不想依赖隐式转换可以在游标里显式to_number(...)更稳妥。3.5 关于批量大小的选择limit 5000不是随便定的。太小循环次数多、commit 频繁太大单次 PGA 占用高。经验值在 1000 到 10000 之间900 万行用 5000 大约 1800 批。你可以用表格对照一下limit 值批次数900万行适用场景10009000单行宽、内存紧张50001800通用推荐10000900行窄、追求吞吐4. 验证请求与成功结果怎么确认批量同步真的跑通了代码写完不代表跑通必须验证。这一节给一套可执行的验证步骤从执行过程到核对数据量。4.1 执行存储过程在 SQL*Plus 或 PLSQL Developer 里执行set timing on exec p_login; exec p_fact;set timing on会打印耗时900 万行用 5000 批量正常在几分钟级别取决于网络和 IO。4.2 核对数据量执行完立刻核对源和目标行数是否一致-- 远程源表行数 select count(1) from user_loginpx_dblink; -- 本地目标表行数 select count(1) from user_login;两个数字必须相等。如果本地少了大概率是最后一批没写入回去检查exit when的位置。4.3 查看日志表日志表能告诉你每批插了多少行、有没有异常select * from t_log order by createtime desc;正常情况你会看到一串batch inserted: 5000最后一条可能是batch inserted: 剩余行数。如果看到错误信息:ORA-xxxxx说明异常块被触发需要根据错误码排查。4.4 验证汇总结果对子任务 B验证汇总是否正确select statedate, sum(login_cn) from fact_login_cn group by statedate order by statedate;再和源表直接汇总对比select to_char(login_time,yyyymmddhh24), count(1) from user_login group by to_char(login_time,yyyymmddhh24) order by 1;两边每个小时的计数应该一致。如果对不上检查group by里是否漏了user_id——题目要求是按「用户 小时」汇总不是只按小时。4.5 用模型对话辅助读执行计划如果你想确认forall是否真的走了批量绑定可以把执行计划贴到模型对话里让它解读https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite把explain plan for insert ...的输出贴进去问「这里有没有发生逐行绑定」能帮你判断批量是否生效。5. 本篇常见错排查401、ORA-02019、ORA-06550 逐个拆这一节按真实报错来每个错误给现象、原因、修法。5.1 ORA-02019: connection description for remote database not found现象执行select count(1) from user_loginpx_dblink时报这个错。原因dblink 名字拼错或者 dblink 建在了别的 schema 下当前用户看不到。修法查一下当前用户能看到的 dblinkselect owner, db_link, host from all_db_links;确认px_dblink存在且 owner 正确。如果不存在重新执行 3.2 的建链语句。5.2 ORA-06550 / PLS-00306: wrong number or types of arguments现象调用p_login时报参数类型不匹配。原因游标select的字段顺序和bulk collect into的变量顺序不一致或者类型不兼容。比如login_time是date你却声明成了number。修法逐个核对游标字段和集合类型。建议用%type锚定减少手写类型出错type type_login_time is table of user_login.login_time%type;5.3 ORA-01555: snapshot too old现象长时间跑批量同步时中途报快照过旧。原因undo 表空间不够或者同步时间太长游标一致性读需要的 undo 被覆盖。修法调大 undo 表空间或延长undo_retention同时把limit调小、commit 更频繁缩短单次事务跨度。5.4 401 UnauthorizedAPI 侧现象调用模型接口时返回 401。原因API Key 没带、带错或者 Base URL 写成了带路径的形式。修法确认三件套齐全——Base URL 用https://taotoken.net/apiKey 从 API Keys 页面复制Model ID 填对。检查配置里有没有多余空格。相关文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite5.5 local proxy failed现象请求发出后报本地代理失败。原因本地网络配置里挂了不可用的代理或者代理端口写错。修法检查系统代理设置把 API 请求走直连确认base_url没有被本地代理规则拦截。5.6 reading choices 相关报错现象解析响应时报reading choices之类的字段缺失。原因返回体结构和预期不符通常是 Model ID 填错导致返回了错误结构或者请求根本没成功。修法先用模型对话页面确认模型可用再核对配置里的 Model ID 拼写。5.7 OAuth 相关报错现象用 Claude Code 类工具时报 OAuth 失败。原因认证方式选错或者 token 过期。修法改用 API Key 方式接入参考接入文档重新配置。Claude Code 的接入说明在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite5.8 排错速查表报错大概率原因第一步动作ORA-02019dblink 不存在查 all_db_linksORA-06550类型/参数不匹配核对游标字段顺序ORA-01555undo 不足调小 limit、加 undo401Key/URL 错核对三件套local proxy failed代理拦截检查系统代理reading choicesModel ID 错核对模型名6. 把骨架用起来从跑通一次到长期批量同步跑通一次只是开始。真正落地时你会需要把p_login挂到定时任务里或者用 Coding Plan 把「生成过程骨架 解释报错」做成日常流程https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite几个实战经验都是踩过坑总结的第一先小批量试跑再放全量。把游标加个where rownum 10000确认逻辑对了再放开。900 万行跑一半发现字段错位回滚都来不及。第二日志表别省。t_log里记录每批行数和时间出问题时能精确定位到哪一批断了。我见过有人为了省事不写日志结果同步少了几万行查了一整天。第三forall的索引从 1 开始。forall i in 1 .. v_user_id.count如果集合是空的count为 01 .. 0不会执行这是安全的。但如果你写成forall i in v_user_id.first .. v_user_id.last空集合时first和last都是 null会报错。第四commit 频率和性能要平衡。每批 commit 一次1800 批就是 1800 次 commitredo 写入频繁但安全如果改成每 10 批 commit 一次性能好一点但异常时回滚范围大。按你的数据重要性选。第五汇总过程注意隐式转换。to_char出来的字符串插进number列数据量大时隐式转换会拖慢速度建议在游标里就to_number转好。最后给一个可以直接复用的调用模板把两个过程串起来begin p_login; -- 先迁移 p_fact; -- 再汇总 dbms_output.put_line(sync done at || to_char(sysdate,yyyy-mm-dd hh24:mi:ss)); end; /这套骨架的价值在于它不依赖任何外部调度框架纯 PLSQL 就能完成跨库批量同步改改游标 SQL 就能复用到别的表。你把px_dblink换成自己的链名把字段换成目标表的字段剩下的循环、批量、日志、异常处理逻辑原样保留即可。
网站建设高端定制企业官网