电科金仓 OID 和 ROWID,这两天折腾迁移的一点碎碎念本

📅 2026/6/29 21:35:15
电科金仓 OID 和 ROWID,这两天折腾迁移的一点碎碎念本
摘要本文以电科金仓KES替换 Oracle 11g 的政务云迁移实战为背景深入解析 OID 与 ROWID 的双轨架构OID 是内核管理数据库对象的表级标识不可用于业务主键ROWID 是行级物理地址伪列适用于增量同步断点续传、孤儿数据清洗及性能调优但会因数据变更而改变不能作为长期业务标识。文章通过 SQL 案例与踩坑经验强调国产数据库迁移需摒弃 Oracle 思维定式合理利用底层特性方能提升效率。目录1.先说说 OID这玩意儿真别乱碰2.ROWID这才是行级寻址的硬通货3.迁移实战用 ROWID 搞定增量同步4.数据清洗用 ROWID 干掉孤儿数据5.避坑指南全是血泪史6.性能调优的一些零碎经验7.写在最后本来这周打算把上个月攒下的绩效考评填完就休息一下结果周一早上刚到工位还没来得及泡第二杯咖啡兄弟部门那个平时不太爱说话的开发组长老张就直接把我拉进了腾讯会议。屏幕共享一打开满屏的 SQL 报错和一堆红色的告警邮件差点把我晃晕。背景是他们负责的一个省级政务云平台要做国产化替换源端是那套跑了快十年的 Oracle 11g目标端选的是电科金仓KingbaseES后面我都简称 KES。数据量倒不是说大到离谱核心交易表也就三千万出头但业务逻辑那叫一个乱各种历史遗留的触发器、存储过程还有一堆我看着都头疼的动态 SQL。老张一开口就是那个经典问题“咱们 KES 里有没有类似 Oracle 那种 ROWID 的东西我们现在的增量同步脚本全靠那个定位。”我当时心里咯噔一下。说实话这问题我听过太多次了。很多从 Oracle 转过来的团队潜意识里总觉得“国产数据库肯定得兼容 Oracle 的那套玩法”甚至有人觉得 ROWID 就是个通用的标准。但真要在企业级环境里玩转电科金仓你得先搞清楚它底层的双轨架构——也就是OID​ 和ROWID​ 这两套东西到底怎么分工谁管对象谁管行什么时候能用什么时候用了就是给自己埋雷。趁着这两天迁移告一段落脑子里的细节还记得住赶紧把这些坑和心得记下来。这篇东西我不打算写成那种教科书式的标准文档太干了后面接手的人估计看两页就关了。我就按咱们平时聊天的逻辑想到哪说到哪尽量把那些官方手册里没写的潜规则给扒出来。1.先说说 OID这玩意儿真别乱碰咱们先从 OIDObject Identifier说起。很多人第一次看电科金仓的系统表比如sys_class、sys_type这些都会发现里面有个oid字段。我刚接触这数据库的时候也犯过傻以为这玩意儿跟 Oracle 的 ROWID 差不多是行的唯一标识甚至还想拿它当业务主键用。后来被当时的架构师大佬指着鼻子骂了一顿说这东西是给内核用的不是给你业务用的。OID 的本质是什么它是电科金仓内核用来给数据库对象发身份证的。你建一张表建一个索引建一个视图甚至是建一个序列内核都会在系统表里给它登记一个全局唯一的整数 ID这就是 OID。你可以随便找个测试库试试SELECT oid, relname, relkind FROM sys_class WHERE relname LIKE t_order%;这条语句查出来的oid就是那张表在数据库字典里的编号。注意啊这是表级别的不是行级别的。也就是说一张表不管有一万行还是一亿行它的 OID 只有一个。这里有个坑得重点提一下。老版本的 KES具体版本号我记不清了大概是 V7 或者更早的某些分支在建表的时候默认行为是WITH OIDS。这意味着如果你不显式声明每行数据都会多出一个隐藏的oid字段。那时候确实有开发拿着这个当行标识用。但现在的新版本V8 及以后默认是关闭这个特性的。为什么要关原因很简单也很致命第一性能问题。OID 字段默认是没有索引的。你拿一个没索引的整数去几百万、几千万的表里查那就是全表扫描慢得你想砸键盘。第二回卷风险。OID 是 32 位的最大值大概是 42 亿。听起来很多但在一个 7x24 小时运行的系统里创建对象、临时表、索引OID 是会一直涨的。万一哪天真回卷了Wrap Around系统直接给你来个逻辑损坏到时候哭都来不及。第三迁移麻烦。你现在用 OID 当主键万一哪天要迁到别的数据库或者升级大版本OID 不一定能保得住。所以我现在跟团队定规矩OID 只能在写运维脚本、查系统元数据的时候用业务代码里要是敢出现SELECT ... WHERE oid ...直接打回重改。​ 它就是个后台管理员别让它跑到前台来接客。2.ROWID这才是行级寻址的硬通货说完 OID再聊回老张关心的那个 ROWID。在电科金仓里真正跟 Oracle 的 ROWID 对标的就是这个伪列Pseudo Column。这东西很有意思。它不是你建表的时候定义在 DDL 里的也不是一个普通的字段它是内核在查询的时候动态算出来的物理地址编码。你可以随便找张表试一下SELECT ROWID, * FROM t_user LIMIT 10;出来的那串字符长得有点像 Base64 编码看着乱七八糟但其实里面藏着这行数据在硬盘上的精确坐标。虽然不同版本的内核实现细节可能有微调但逻辑上基本都包含这几块信息对象号对应表 OID、文件号、块号Block Number、行号Slot Number。翻译成人话就是哪个数据文件、第几个块、块里的第几行。为什么 DBA 都喜欢这东西因为它快快到不讲道理。想象一下你有一张 5000 万行的大表上面只有一个主键索引而且你的查询条件刚好没用上这个索引。正常情况下数据库得把 5000 万行数据全部读一遍过滤出符合条件的结果。但如果这时候你知道你要找的那行数据的 ROWID数据库根本不用扫索引也不用扫全表直接拿这个地址去算文件偏移量去硬盘上那个位置把数据抠出来就行。这叫物理寻址Direct Access速度差几个数量级。上次我们有个财务结算的脚本要更新一张大表里几万行状态异常的数据。开发写的 SQL 是按条件UPDATE没走索引跑了 40 分钟还没完。我过去看了一眼直接给他改成两步第一步先按条件把 ROWID 查出来存到临时表第二步根据 ROWID 去更新。最后总共花了不到 3 分钟。老张当时看我的眼神都变了说这也行3.迁移实战用 ROWID 搞定增量同步回到这次迁移。老张他们的痛点在于源库 Oracle 的数据一直在写不能停业务。所以他们想搞增量同步也就是第一次全量迁完后面每隔几分钟抓一次变化的数据。在 Oracle 那边他们原来的方案是利用ORA_ROWSCN或者ROWID范围。到了 KES我们商量了一下决定用 ROWID 做切片。具体怎么做呢我们写了一个脚本先去源库Oracle里按 ROWID 把表的数据切成一百份。怎么切利用DBMS_ROWID包或者干脆按ROWID的字符串前缀分桶。每一份大概几十万行。然后我们用 Kettle现在叫 Pentaho或者自己写的 Java 程序把这几百个分片并发往 KES 里灌。中间出了几次岔子比如网络抖动、目标库锁表导致导到一半失败了。这时候 ROWID 的优势就体现出来了我不需要回滚也不需要重新导整个表我只需要在日志里记下最后成功导入的那个 ROWID 分片的起点下次从那个点接着往后导就行。这就叫断点续传。如果你用LIMIT OFFSET来做分页迁移OFFSET 大到一定程度性能会断崖式下跌而且中断了很难精准续上。这里有个细节得注意。电科金仓的 ROWID 编码规则和 Oracle 是不一样的。你不能指望 Oracle 导出来的 ROWID 字符串直接插到 KES 里还能用。我们的做法是在源端按 ROWID 分片是为了控制导出粒度在目标端KES侧我们是用自增的时间戳或者序列来做的幂等校验。别想着直接复用 ROWID 的值那是给自己挖坑。4.数据清洗用 ROWID 干掉孤儿数据迁移过程中数据清洗也是个大头。老系统里很多表当初建的时候没加外键约束或者加了后来又给禁用了。结果就是子表引用了父表的数据父表的数据删了子表里剩下一堆指向空值的孤儿。比如有个t_order_detail表里面的order_id理论上应该在t_order表里存在。但实际情况是一查发现有十几万行order_id在t_order里找不到。开发的第一反应是用NOT INDELETE FROM t_order_detail WHERE order_id NOT IN (SELECT id FROM t_order);这条 SQL 在小表上没问题但在几千万的大表上子查询先扫一遍t_order然后外层再扫一遍t_order_detail还要做 Hash 反连接慢得离谱。我们当时跑了俩小时都没动静。我给的方案是改成用 ROWID 关联删除DELETE FROM t_order_detail WHERE ROWID IN ( SELECT d.ROWID FROM t_order_detail d LEFT JOIN t_order o ON d.order_id o.id WHERE o.id IS NULL );逻辑是一样的都是找孤儿。但区别在于内层查询虽然还是要 JOIN但最后返回的是t_order_detail的 ROWID。外层 DELETE 的时候数据库直接拿着这些 ROWID 去物理定位行不需要再回表查一次主键或者整行数据I/O 压力小很多。那天改完之后十几万行数据大概几十秒就清完了。5.避坑指南全是血泪史写了这么多实战也得泼点冷水。ROWID 虽然好用但它有个最大的特点它会变。什么时候会变表重组Alter Table Move如果你对表做了ALTER TABLE ... MOVE TABLESPACE或者做了在线重定义数据的物理位置变了ROWID 必变。行迁移Row Migration如果你更新了一行数据导致这行数据变大了原来的数据块放不下了数据库会把这行挪到新的块里。这时候 ROWID 也会变。导出再导入不管是逻辑导出sys_dump还是物理备份恢复只要数据重新写过ROWID 大概率会变。所以有几个铁律我必须强调绝对不要把 ROWID 存到业务表里当永久关联键。​ 这跟自杀没区别。今天你存的 ROWID明天表一移动全废。不要用 ROWID 做分页查询。​ 网上有些所谓的优化秘籍教人用 ROWID 做分页比如WHERE ROWID ...。在 KES 里除非你非常确定数据不会变否则千万别这么干。分页结果会乱套的。注意兼容模式。​ 电科金仓安装的时候可以选择兼容模式Oracle、PostgreSQL 等。如果你选的不是 Oracle 兼容模式可能压根不支持ROWID这个伪列。建库前一定要规划好后期改模式比迁数据还痛苦。6.性能调优的一些零碎经验除了上面那些平时运维 KES 的时候针对 OID 和 ROWID我还有几个习惯性的操作监控 OID 消耗。​ 虽然现在新版本 OID 回卷的概率低了但作为 DBA我还是习惯定期查一下sys_class里 OID 的最大值看看增长趋势。特别是那种频繁建临时表的应用OID 消耗很快。批量处理用 ROWID 分桶。​ 如果你要对大表做全表扫描类的批量处理比如算个复杂的统计可以按 ROWID 把数据分成若干份开多线程并发处理。因为 ROWID 代表了物理分布分桶后的数据在磁盘上相对连续能减少随机 I/O。留意执行计划里的 ROWID 字样。​ 有时候你看到执行计划里有Bitmap Heap Scan或者Table Scan后面跟着Filter: (ROWID ...)说明优化器在用 ROWID 做过滤。这时候要看一下代价是否合理有时候统计信息不准会导致优化器误判。7.写在最后OID 和 ROWID 这套双轨架构其实是电科金仓在企业级场景下做的一种权衡。OID 保证了内核管理对象的效率和稳定性ROWID 提供了极致性能的物理寻址能力。对于开发人员和 DBA 来说理解这两者的边界至关重要。别总想着拿数据库底层的特性去炫技。OID 就老老实实待在系统表里ROWID 就在你救火、迁数据、清脏数据的时候拿出来用用。业务代码里该建主键建主键该建索引建索引别老想着走捷径。这次迁移搞完我最大的感触是数据库这东西越是底层的东西越危险。用好了是神器用不好就是定时炸弹。特别是现在信创环境下大家都在学国产数据库千万别拿以前 Oracle 的经验生搬硬套。多看看执行计划多测测性能少踩点坑咱们早点下班回家陪老婆孩子不香吗行了就写到这。明天还得去给那帮开发做培训讲讲怎么写规范的 SQL。希望能帮到他们吧至少别再半夜给我打电话喊救命了。金仓社区 - 电科金仓官方技术社区10 个关键字解说序号关键字解说1OID内核给数据库对象表、索引等分配的全局唯一整数 ID表级标识严禁用于业务主键。2ROWID内核动态计算的行物理地址伪列包含文件号、块号、行号可实现物理寻址直接访问。3双轨架构KES 中 OID 管对象、ROWID 管行级的两套独立标识体系分工明确、不可混用。4物理寻址通过 ROWID 直接计算磁盘偏移量读取数据绕过索引与全表扫描性能提升数个数量级。5回卷Wrap Around32 位 OID 达 42 亿上限后溢出回绕导致对象标识冲突是禁用 OID 于业务的核心原因。6增量同步全量迁移后持续捕获源库变更数据本文用 Oracle ROWID 分片实现不停机迁移。7断点续传利用 ROWID 分片记录成功起点中断后精准续传避免 LIMIT OFFSET 性能断崖问题。8孤儿数据子表外键在父表中找不到对应记录的悬空数据本文用 ROWID 关联删除替代 NOT IN 优化性能。9全表扫描无索引条件下逐行读取整张表的 I/O 操作OID 无索引必触发此问题代价极高。10信创/国产化替换将 Oracle 等国外数据库替换为国产 KES 的过程需重新理解底层特性而非生搬硬套。免责声明本文纯属个人工作笔记整理不代表公司官方观点。文中涉及的 SQL 示例仅供参考生产环境操作前请务必在测试环境充分验证。