背景
平时跟数据库打交道,OID 和 ROWID 这俩“隐藏字段”经常半路冒出来,看着很像,用起来差得不是一星半点。很多人要么把它俩混为一谈,要么只知道能用、却不知道为啥这么设计、踩过哪些坑。

本文结合实测案例,从KingbaseES的底层原理、全局 / 局部特性、创建方式、类型别名到实战坑点,完整拆解 OID 与 ROWID 的核心知识,帮你彻底分清两者边界,避开生产环境常见误用。
一、先认识 OID:数据库对象的“内部工号”
OID 就是 Object Identifier,对象标识符。你可以把它理解成数据库内部给各种对象发的唯一工号——表、函数、类型、索引、视图……全都有一个。
它是 4 字节整数,数据库内核靠它管理元数据,系统表基本都拿 OID 当主键。
1. OID 有个很关键的特点:分层生效
很多人一上来就懵:为啥有时候 OID 全局唯一,有时候又只在表里自增?
其实很简单:
- 系统表的 OID:全局统一发号
数据库有一个全局计数器,新建任何对象都从这里拿号。所以你新建一个类型,再建一个函数,它们的 OID 往往是连着的,跨系统表不重复。 - 普通用户表的 OID:局部自增,每张表从头算
这一点和 PG 不一样。在咱们常用的这款企业级数据库里,普通表即使开了 OID,也是表内独立编号,从 1 开始,不会跟别的表抢号。
2. 默认看不见,得手动“请出来”
默认建表,OID 是不存在的。你直接 select oid from 表 直接报错:列不存在。
想让表带上 OID,只有两种方式:
- 改参数:
default_with_oids = true - 建表时写:
create table ... with oids;
开了之后它还是“隐藏列”,select * 不显示,必须手动写 oid 字段才能查出来。
3. 神器 regclass:OID 的“快捷马甲”
写系统表查询时最烦的一件事:
想查一张表的字段,得先去 sys_class 查 OID,再关联 sys_attribute。
有了 regclass 就舒服多了:
-- 直接把表名转成对应 OID
select * from sys_attribute where attrelid = 'teachers'::regclass;
::regclass 本质就是 OID 的别名,相当于帮你自动执行了一次:
select oid from sys_class where relname = 'teachers';
系统表查询瞬间清爽很多,这也是日常开发里 OID 最实用的姿势。
4. OID 真不适合当业务主键
别看它能唯一标识,别拿来当业务主键用:
- 4 字节上限大约 42.9 亿,用完会绕回来,可能重复
- 普通表 OID 是局部的,跨表根本标识不了行
- 不会自动建唯一索引,大量插入时冲突检测巨慢
正经做主键,用 serial 或 bigserial 比它靠谱得多。
二、再看 ROWID:每行数据的“专属座位号”
如果说 OID 管的是对象,那 ROWID 管的就是行。
它是数据库给每一行记录分配的逻辑唯一标识,不是物理地址,但能做到全局唯一、快速定位,有点像 Oracle 里那味儿,但底层实现是自研逻辑。
1. ROWID 一开启,自带“VIP 索引”
ROWID 最爽的一点:
你只要 with rowid 建表,数据库自动给你建一个唯一 btree 索引。
create table student(...) with rowid;
查完表结构你会看到:
Indexes:"student_rowid_key" UNIQUE CONSTRAINT, btree ("rowid")
查询直接按 ROWID 定位,速度极快。
2. 开启方式很灵活,但和 OID 互斥
ROWID 有三种启用姿势:
- 开全局参数:
default_with_rowid = true - 建表指定:
with rowid - 已有表追加:
alter table ... set with rowid;
这里有个重要规则:
ROWID 优先级 > OID
两个参数同时开,只生效 ROWID,OID 直接被屏蔽。
如果你开了 OID、想强行建 ROWID 表,数据库会直接报错阻止你,避免混乱。
3. ROWID 长什么样?一串神秘字符串
你查出来大概是这种:
AAAAAAAAADP5AAAAAAAAAAA
一共 23 个字符,用 64 进制编码(A-Z、a-z、0-9、+/),内部拆成三部分信息:
- 事务回卷次数
- 事务 XID
- 事务里插入的行号
所以它是严格单调递增的:后插的行,ROWID 一定更大。
支持所有比较运算符:
select * from t where rowid > 'xxx';
排序、分组、where 条件都能用,非常丝滑。
4. 用 ROWID 要注意几件事
- 长度必须是 23 位,多了少了都插不进去
- 只支持大小、等于比较,不支持加减乘除
- 支持 btree、hash 索引,别硬上其他索引类型
- 存储过程里可以正常用,但别玩太花
三、OID vs ROWID
- OID和ROWID对比速记图

- OID与 ROWID详细对比
| 对比项 | OID | ROWID |
|---|---|---|
| 管什么 | 数据库对象(表、函数、类型) | 数据行记录 |
| 作用范围 | 系统表全局,普通表局部 | 全局逻辑唯一 |
| 数据类型 | 4 字节整数 | 23 位 64 进制字符串,变长存储 |
| 默认可见性 | 完全隐藏,* 不显示 |
隐藏,需显式查询 |
| 自动索引 | 无 | 自带唯一 btree 索引 |
| 开启方式 | with oids / 参数开关 |
with rowid / 参数开关 / alter |
| 与对方关系 | 优先级低,会被 ROWID 覆盖 | 优先级高,互斥生效 |
| 单调递增 | 系统表全局递增,表内从 1 开始 | 严格全局递增,后插更大 |
| 适合场景 | 系统表查询、元数据管理 | 快速行定位、批量处理、高效查询 |
| 风险点 | 42 亿后绕回,可能重复 | 几乎无上限,稳定性更强 |
| 业务主键建议 | 强烈不推荐 | 可用于内部快速定位,不建议业务暴露 |
四、顺便说一句::: 这个符号到底在干嘛?
文章里一堆 ::,比如 'teachers'::regclass、'25'::integer。
它就是强制类型转换,和 CAST( … AS …) 完全等价,只是写法更短。
-- 这俩效果一模一样
select '25'::integer;
select cast('25' as integer);
在系统表查询、特殊类型转换里,:: 几乎是标准写法,看着专业,写着省事。
五、总结
- OID 是给数据库对象用的,系统表用它全局唯一,普通表开了也只是表内自增,别当业务主键。
- ROWID 是给数据行用的,全局唯一、自带索引、查询飞快,适合快速定位行。
- 俩东西互斥,ROWID 说了算,别同时乱开参数。
- regclass 和
::是写系统 SQL 的神器,学会少写很多嵌套子查询。
整体理解下来就一句话:
OID 管对象,ROWID 管行;一个管元数据,一个管提速。
