在构建后端架构或微服务系统时,数据唯一标识是绕不开的核心问题。金仓数据库(KingbaseES)提供了三种方案:OIDROWID自增主键。它们各司其职,选错了可能影响查询性能或运维效率。本文从原理到实战,帮你理清三者的区别与适用场景。

一、OID:数据库的全局身份证

OID(Object Identifier)是数据库内部的4字节无符号整数,为每个对象(表、视图、索引等)分配唯一编号。在系统目录中,OID是全局唯一的,例如 sys_classsys_attribute 等系统表通过OID互相关联,形成元数据网络。

但普通用户表默认不携带OID,需要显式启用:

-- 看看 sys_type 里有多少条记录
test=# SELECT count(*) FROM sys_type;
count
-------
1208
(1 row)
-- 其中超过半数的 OID 都大于 1208,说明系统预分配了大量 OID
test=# SELECT count(*) FROM sys_type WHERE oid > 1208;
count
-------
1120
(1 row)
-- 再看看 OID 最大值附近,还有在持续分配的
test=# SELECT oid FROM sys_type WHERE oid > 33990;
oid
-------
33992
33993
33995
(3 rows)

OID在系统表中是全局共享,在普通表中却是局部自增——表A的OID=1与表B的OID=1毫无关联。这种设计让OID更适合DBA运维场景,而非业务标识。

regclass 类型是OID的“翻译官”,可将表名自动转为OID,简化元数据查询:

-- 创建一个复合类型
cpbd_test=> CREATE TYPE aatyp IS TABLE OF int;
CREATE TYPE
cpbd_test=> SELECT oid, typname FROM sys_type WHERE typname = 'aatyp';
oid  | typname
-------+---------
55136 | aatyp
(1 row)
-- 再创建一个函数
cpbd_test=> CREATE OR REPLACE FUNCTION func_test05(i int)
RETURN int AS vv int; BEGIN RETURN 1; END;
CREATE FUNCTION
cpbd_test=> SELECT oid, proname FROM sys_proc WHERE proname = 'func_test05';
oid  |  proname
-------+------------
55137 | func_test05
(1 row)

二、ROWID:KES独有的行定位利器

ROWID是金仓数据库独创的行标识机制,以隐藏列形式物理存储,并自动创建B-tree唯一索引。与OID不同,ROWID不仅标识行身份,还记录了物理位置,支持范围查询(BETWEEN> 等)。

启用方式有三种:

  • 全局参数:设置 default_with_rowid = on
  • 建表指定CREATE TABLE ... WITH (ROWID)
  • 后期追加ALTER TABLE ... SET ROWID
test=# CREATE TABLE tt5(id int);
CREATE TABLE
test=# INSERT INTO tt5 VALUES(10);
INSERT 0 1
test=# SELECT oid, id FROM tt5;
ERROR:  column "oid" does not exist
LINE 1: select oid,id from tt5;
^
HINT: Perhaps you meant to reference the column "tt5.id".

⚠️ 注意:OID与ROWID互斥,一张表不能同时拥有两者。

三、ROWID内部结构拆解

ROWID显示为23字符的Base64编码字符串,实际存储仅需4-18字节(变长编码)。结构分为三段:

test=# SET default_with_oids TO true;
SET
test=# CREATE TABLE tt6(id int);
CREATE TABLE
test=# INSERT INTO tt6 VALUES(10);
INSERT 0 1
test=# SELECT oid, id FROM tt6;
oid | id
-----+----
1 | 10
(1 row)
长度含义
块号前12字符物理块ID
事务ID中间部分生成时的事务标识
命令计数器后11字符事务内语句序号
字符位置位数干什么用的
存储位置(块号)第 1~6 字符32 位这行数据存在哪个存储块
事务 ID(xid)第 7~12 字符32 位插入这行数据的事务编号
命令计数器第 13~23 字符64 位同一个事务里第几条命令

ROWID支持完整比较操作,且与COPY导入导出兼容,适合数据迁移场景。

四、三方案选型对比

维度OIDROWID自增主键
作用域全局(系统表)表级表级
自动索引B-tree唯一索引需手动创建
范围查询不支持支持支持
业务关联
OIDROWIDSERIAL / BIGSERIAL
是什么系统伪列,可选开启物理存储的隐藏列用户自己定义的显式列
数据类型4 字节无符号整数Base64 字符串(rowidtype)整数(int4 或 int8)
唯一范围系统表全局唯一;普通表仅表内仅表内仅表内
物理存储元组头部,不占用户列空间隐藏列,变长 4~18 字节用户列,固定 4 或 8 字节
索引无自动索引自动建 B-tree 唯一索引需要手动建主键或唯一约束
上限~42.9 亿(有回卷风险)编码空间远大于 OIDSERIAL ~21 亿;BIGSERIAL ~9.2×10^18
适合干什么系统目录查询行级定位、快速查找业务主键、外键关联
参数控制不需要 GUC 参数

选型建议

  • 系统目录查询 → OID + regclass
  • 快速行定位、调试 → ROWID
  • 业务唯一标识 → SERIAL/BIGSERIAL + PRIMARY KEY

五、GUC参数配置与生产避坑

与OID/ROWID相关的两个GUC参数:

参数类型默认值作用
BOOLfalse新建表是否默认带 OID 列
BOOLfalse新建表是否默认带 ROWID 列

四种组合的建表行为:

建出来的表
falsefalse什么都没有——普通表
truefalse带 OID,没有 ROWID
falsetrue带 ROWID,没有 OID
truetrueROWID 赢——带 ROWID,没有 OID

唯一会报错的组合是:全局关闭ROWID、开启OID,却试图创建带ROWID的表。错误信息会明确提示冲突。

生产环境五条建议

  1. 集群统一配置 default_with_rowiddefault_with_oid
  2. 优先使用ROWID而非OID作为行标识
  3. DDL中显式声明,不依赖全局参数
  4. 同一数据库内避免混用OID和ROWID
  5. 业务主键不可替代,OID/ROWID仅是内部手段
[AFFILIATE_SLOT_1]

六、实战:用OID串联系统目录

OID在元数据查询中扮演“粘合剂”角色。核心系统表关系如下:

test=# SET default_with_oids TO false;
SET
test=# CREATE TABLE tt7(id int) WITH OIDS;
CREATE TABLE
test=# INSERT INTO tt7 VALUES(10);
INSERT 0 1
test=# SELECT oid, id FROM tt7;
oid | id
-----+----
1 | 10
(1 row)

实战场景示例:

  • 查表的列信息:SELECT ... FROM sys_attribute WHERE attrelid = 'mytable'::regclass
  • 查索引信息:通过 sys_index 关联 sys_class
  • 查自定义类型:SELECT oid, typname FROM sys_type WHERE typtype = 'c'
-- 直接把 OID 翻译成表名
test=# SELECT 16700::regclass;
regclass
----------
teachers
(1 row)

::CAST 功能等价,按需选用。

[AFFILIATE_SLOT_2]

总结

OID、ROWID和自增主键各有定位:OID适合系统目录查询,ROWID擅长行级定位与范围过滤,自增主键是业务唯一性的基石。合理选型能提升后端架构的可维护性和查询效率。记住:业务主键不能省,行标识按需选

OID 是 KES 系统内部的"编号系统",在系统表里全局唯一,配合 做元数据查询非常顺手。但它有 4 字节的天花板,普通表里又是局部的,不适合做业务主键。

ROWID 是 KES 自己搞出来的行标识方案——物理存储、自动建唯一索引、支持范围查询和排>序分组,定位一行数据比 OID 精准得多。

SERIAL / BIGSERIAL 则是业务主键的标准选择,配合 PRIMARY KEY 约束,适合绝大多数应>用场景。

三者各管一段,搞清楚谁该在什么场合出场,你就算是把金仓数据库的行标识机制真正吃透了。

default_with_oidsdefault_with_rowiddefault_with_oidsdefault_with_rowiddefault_with_oidsdefault_with_rowidregclass