KingbaseES 行标识机制全解:OID、ROWID 与自增主键的底层逻辑

2 阅读7分钟

引言

在关系型数据库的底层设计中,"如何唯一标识一行数据"是绕不开的核心问题。KingbaseES(KES)给出了自己的答案——不止一套,而是三套并行的机制:OID、ROWID、以及自增主键

三者看似都在解决"行唯一性"这件事,实则定位迥异,各有专属的使用场景与技术边界。本文从源码设计出发,系统梳理这三套机制的实现原理、参数治理规则与工程实践中的选择逻辑。


一、OID:数据库对象的全局编号体系

1.1 OID 的双重身份

OID(Object Identifier,对象标识符)在 KES 中承担着两个截然不同的角色,这是很多人产生混淆的根源。

角色一:系统表的全局主键

KES 的绝大多数系统目录表(sys_classsys_typesys_proc 等)都以 OID 作为主键,且这个 OID 是跨系统表全局递增的。下面这段实验可以直接说明问题:

-- 先建一个集合类型,sys_type 中 OID 落在 55136
CREATE TYPE aatyp IS TABLE OF INT;

SELECT oid, typname FROM sys_type WHERE typname = 'aatyp';
--  OID  | TYPNAME
-- -------+---------
--  55136 | aatyp

-- 紧接着建一个函数,sys_proc 中 OID 落在 55137
CREATE OR REPLACE FUNCTION func_test05(i INT) RETURN INT AS ...

SELECT oid, proname FROM sys_proc WHERE proname = 'func_test05';
--  OID  |   PRONAME
-- -------+-------------
--  55137 | func_test05

两个不同系统表、两类不同对象,OID 连续递增——这不是巧合,而是 KES 全局 OID 生成器的设计结果。正因如此,你可以用一个 OID 值在整个数据库中定位到唯一的对象。

角色二:普通表的局部行号

这是 KES 与 PostgreSQL 的一处重要分叉:

特性PostgreSQL 普通表 OIDKES 普通表 OID
作用域全局唯一仅表内局部
默认开启历史版本默认开启默认关闭
计数方式全局递增每表从 1 起独立计数

这意味着在 KES 中,不同表里可能存在 OID 值都为 1 的行,跨表用 OID 做关联是无效的。

1.2 如何为普通表开启 OID

-- 方式一:修改 GUC 参数(影响后续所有新建表)
SET default_with_oids TO true;
CREATE TABLE tt6(id INT);

-- 方式二:建表时显式声明
CREATE TABLE tt7(id INT) WITH OIDS;

-- 查询时通过伪列访问
SELECT oid, id FROM tt7;
--  oid | id
-- -----+----
--    1 | 10

OID 是 4 字节无符号整数,上限约 42.9 亿。超出后会循环重用,没有内置去重保障——这是它不适合做业务主键的根本原因。

1.3 regclass:OID 的快捷访问通道

KES 提供了一个实用的类型别名 regclass,让 OID 与对象名称之间的转换变得极为简洁:

-- 用 OID 反查表名
SELECT 16700::regclass;
--  REGCLASS
-- ----------
--  teachers

-- 在系统表查询中替代子查询
-- 传统写法(冗长)
SELECT attname FROM sys_attribute
WHERE attrelid = (SELECT oid FROM sys_class WHERE relname = 'teachers');

-- regclass 写法(简洁)
SELECT attname FROM sys_attribute
WHERE attrelid = 'teachers'::regclass;

'表名'::regclass 本质上是一次类型转换,等价于从 sys_class 中查 OID,但省去了显式 JOIN,在编写元数据查询脚本时效率提升明显。


二、ROWID:KES 特有的行逻辑标识

2.1 设计定位

ROWID 是 KES 针对业务场景专门设计的行级逻辑唯一标识,在国产数据库中对标 Oracle 的 ROWID 概念,但实现机制有本质区别——KES 的 ROWID 是逻辑 ID,不是物理存储地址

这意味着:

  • 行迁移、页面整理等物理操作不会改变 ROWID
  • ROWID 在整个数据库范围内单调递增
  • 系统自动维护,无需开发者干预

2.2 开启方式与优先级规则

-- GUC 参数方式(全局生效)
SET default_with_rowid TO true;
CREATE TABLE tt11(id INT);
INSERT INTO tt11 VALUES(10);

SELECT rowid, id FROM tt11;
--          ROWID          | ID
-- -------------------------+----
--  AAAAAAAAADQCAAAAAAAAAAA | 10

建表时也可单独声明:

CREATE TABLE student(
    sno        INT,
    name       VARCHAR(10),
    birthday   DATE,
    department VARCHAR(10),
    sex        VARCHAR(10)
) WITH ROWID;

建表完成后,系统会自动创建 ROWID 唯一约束索引

Indexes:
    "student_rowid_key" UNIQUE CONSTRAINT, btree ("rowid")

优先级规则是 KES 参数治理中需要特别注意的一点:

default_with_rowid 开启时,default_with_oids 自动失效。

两个参数同时为 true,建表只生成 ROWID;default_with_oids 开启而 default_with_rowid 关闭时,强制用 WITH ROWID 建表会直接报错:

SET default_with_rowid TO false;
SET default_with_oids  TO true;

CREATE TABLE tt16(id INT) WITH ROWID;
-- ERROR: can not create table with rowid
--        (default_with_rowid is false, and default_with_oids is true)!

2.3 ROWID 数据类型深度解析

ROWID 的外观是一个 23 位字符串,字符集为 64 进制(A-Z、a-z、0-9、+、/),实际存储采用 4~18 字节变长结构,编码规则如下:

[事务回卷次数: 0~5位] + [插入时事务XID: 6~11位] + [事务内插入行号: 12~22位]

合法范围:

边界
最小值AAAAAA+AAAAAB+AAAAAAAAAAA
最大值D//////+D//////+P//////////

字符串长度不匹配将直接拒绝写入:

INSERT INTO rowid_tt1 VALUES('AAAAAAAAAAABAAAAAAAAAAA44444444');
-- ERROR: invalid input syntax for type rowid

ROWID 支持的操作场景:

场景支持情况
SELECT 列表
WHERE 条件
ORDER BY
GROUP BY
存储过程调用
算术运算
支持的比较操作符=>>=<<=!=
可建索引类型B-tree、HASH

后插入的行 ROWID 严格大于先插入的行,单调性有保障,这使得 ROWID 可以用来进行基于插入顺序的高效排序与范围扫描。


三、核心机制对比:OID vs ROWID vs 自增主键

三套机制并存,工程实践中该如何选择?下表从多个维度做直接对比:

维度OIDROWIDSERIAL / BIGSERIAL
唯一性范围系统表全局;普通表局部全库全局表内唯一
存储类型4字节整数变长字节(4~18字节)4/8字节整数
默认开启否(需参数或DDL)否(需显式定义)
自动索引有(唯一B-tree)视主键声明而定
物理地址绑定否(逻辑ID)
上限与回绕~42.9亿,会循环理论上限极高BIGSERIAL约922亿亿
适用场景系统表管理、元数据查询业务行快速定位业务主键
业务表推荐⚠️(辅助标识)

核心结论:

  • 系统表操作、元数据查询:用 OID,配合 regclass 等别名类型降低 SQL 复杂度
  • 需要快速行定位、兼容 Oracle ROWID 语义的场景:用 KES ROWID
  • 业务表主键:优先 SERIAL(数据量大时用 BIGSERIAL),语义清晰,索引可控,跨数据库移植性好

四、GUC 参数治理与兼容性风险

4.1 参数组合的四种状态

default_with_oids=false / default_with_rowid=false  →  默认行为,无隐藏列
default_with_oids=true  / default_with_rowid=false  →  新建表含 OID 伪列
default_with_oids=false / default_with_rowid=true   →  新建表含 ROWID 伪列(推荐)
default_with_oids=true  / default_with_rowid=true   →  ROWID 生效,OID 被覆盖

4.2 工程实践中的踩坑点

踩坑一:SESSION 级修改参数后,只影响当前会话内新建的表,重连后恢复默认。生产环境如果要全局生效,必须写入 kingbase.conf

踩坑二:OID 列不会出现在 \d 描述或 SELECT * 结果中,容易误判为不存在。

踩坑三:从 PostgreSQL 迁移到 KES 时,如果业务代码依赖普通表 OID 的全局唯一性,会出现数据定位错误,需在迁移前逐一排查。


五、小结

KES 的行标识体系是一个层次分明的设计:OID 负责系统层面的对象寻址,ROWID 承接业务层面的行逻辑标识,自增主键则是开发者掌控的显式标识。

三者不互相替代,但彼此之间存在优先级规则和参数互斥约束。吃透这套机制,不仅能写出更高效的元数据查询,也能在迁移、调优、问题排查中少踩坑、快定位。