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

引言

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

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


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

1.1 OID 的双重身份

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

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

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

sql 复制代码
-- 先建一个集合类型,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 普通表 OID KES 普通表 OID
作用域 全局唯一 仅表内局部
默认开启 历史版本默认开启 默认关闭
计数方式 全局递增 每表从 1 起独立计数

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

1.2 如何为普通表开启 OID

sql 复制代码
-- 方式一:修改 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 与对象名称之间的转换变得极为简洁:

sql 复制代码
-- 用 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 开启方式与优先级规则

sql 复制代码
-- 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

建表时也可单独声明:

sql 复制代码
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 建表会直接报错:

sql 复制代码
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//////////

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

sql 复制代码
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 自增主键

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

维度 OID ROWID SERIAL / 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 承接业务层面的行逻辑标识,自增主键则是开发者掌控的显式标识。

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

相关推荐
m0_674294642 小时前
TypeScript 5.2 升级引发 NestJS 构建失败的解决方案
jvm·数据库·python
LiAo_1996_Y2 小时前
如何强制phpMyAdmin通过HTTPS安全访问_配置ForceSSL参数与Web代理端证书部署
jvm·数据库·python
weixin_424999362 小时前
如何正确实现“破纪录次数统计”算法(高低分突破计数)
jvm·数据库·python
weixin_586061462 小时前
mysqlSQL执行后连接未断开耗尽资源_优化代码中的连接释放机制
jvm·数据库·python
Wyz201210242 小时前
Golang interface底层实现原理_Golang接口原理教程【核心】
jvm·数据库·python
qq_372154232 小时前
宝塔面板如何快速找回前一天误删的极其重要的网站源码
jvm·数据库·python
Shorasul2 小时前
Vue3 监听器 watch 怎么监听 Pinia 中的状态?跨模块联动开发教程
jvm·数据库·python
m0_734949792 小时前
JavaScript 中的 setTimeout 是否依赖系统时钟?
jvm·数据库·python
2301_817672262 小时前
Python Selenium怎么定位元素_By.XPATH与By.CSS_SELECTOR操作DOM节点
jvm·数据库·python