喵喵宠物医院
86.09M · 2026-04-08
在数据库这个领域里,咱们都知道,怎么给每一行数据一个独一无二的“身份证”,是保证数据靠谱不混乱、操作流畅又高效的根本。KingbaseES(KES)作为咱们自家的国产数据库,在处理这个事儿上有不少自己的门道儿。其中,对象标识符(OID)和行标识符(ROWID)就是它内部管理和元数据访问的两员“大将”。
今天这篇分享,我就从技术实现的角度,带大家把KingbaseES里OID和ROWID这俩概念的底层原理给掰扯清楚。咱们一起来看看它们各自都有啥特点、适合用在哪些场合,还有它们之间那些微妙的“关系”。我会详细说说OID在系统表和用户表里表现为啥不一样,以及KES特有的ROWID机制,包括它的数据类型、操作规范,还有它俩在GUC参数配置上可能闹的“小矛盾”。当然啦,咱们还会把OID、ROWID和大家更熟悉的自增主键放一块儿比一比,再瞅瞅怎么用这些标识符来查询和解析数据库的元数据。希望通过我这篇唠嗑式的分享,能帮大家对KingbaseES行标识符管理的技术细节有个更全面的认识,往后在数据库设计、开发和优化的时候,也能做得更明白、更到位。
@[toc]
在数据库系统里头,给每一行数据一个唯一的身份标识,这活儿是核心中的核心。KingbaseES(KES)在这方面提供了好几种方法,其中对象标识符(OID)就是它内部管理和元数据访问的一个重要角色。
OID(Object Identifier),翻译过来就是对象标识符,它是KingbaseES内部用来给数据库对象(比如表、视图、存储过程这些)和数据行打标签的一个4字节长的标识符。在KES里头,系统表和视图天生就带着OID,而且还把它当主键用。不过话说回来,对于咱们自己建的普通用户表,OID默认可是不会露脸的,也不是所有表都自动配有这玩意儿。
KES版本验证: 我写这篇东西的时候,是基于下面这个KingbaseES版本做的验证:
select version();
咱们自己建的普通表,默认可是没有OID的。如果你想让某个表带上OID,有两种方法:一个是在建表的时候用上WITH OIDS这个子句,另一个是把GUC参数default_with_oids给设置成true。
示例:默认情况下普通表不带OID
create table tt5(id int);
insert into tt5 values(10);
select oid,id from tt5;
从上边这个例子咱们就能看出来,直接去查tt5表的oid列会报错,这说明这列压根儿就不存在。
示例:通过设置default_with_oids启用OID
set default_with_oids to true;
create table tt6(id int);
insert into tt6 values(10);
select oid from tt6;
select * from tt6;
当default_with_oids被设置成true之后,新创建的tt6表就自动有了OID。虽然你用select *是看不到OID列的,但直接查询oid这个伪列是没问题的。
示例:通过CREATE TABLE ... WITH OIDS启用OID
set default_with_oids to false;
create table tt7(id int) with oids;
insert into tt7 values(10);
select oid,id from tt7;
哪怕default_with_oids是false,用WITH OIDS这个子句照样可以给指定的表配上OID。
OID在KingbaseES里头,它的“地盘”大小可不太一样,理解这一点对咱们搞清楚它该用在哪很重要。
系统表中的OID:全局且连续 对于系统表来说,OID是全局唯一的,而且分配的时候大体上是连续的。这就意味着,不同系统表里的对象,它们的OID值可能是挨着的。
---创建一个自定义集合类型,查看其在sys_type 中的OID
create type aatyp is table of int;
select oid,typname from sys_type where typname = 'aatyp';
---创建一个自定义函数,查看其在sys_proc 中的OID
create or replace function func_test05(i int)
returns int
as $$
begin
return 1;
end;
$$ language plpgsql;
select oid,proname from sys_proc where proname = 'func_test05';
从上面这个例子咱们就能发现,新创建的类型aatyp和函数func_test05在它们各自的系统表sys_type和sys_proc里,拿到的OID是连着的(55136和55137),这正好印证了系统表OID的全局连续性。
普通表中的OID:局部 跟系统表不一样,KingbaseES里普通表的OID是“各自为政”的,也就是说,每个普通表都有自己的OID序列,跟别的表不掺和。这意味着哪怕两个不同的普通表都启用了OID,它们的OID序列也是各自从1开始重新计数的。
SERIAL或者BIGSERIAL这种数据类型来当主键,别太依赖OID。SERIAL适合估计数据量不会超过20亿的表,而BIGSERIAL呢,则是给那些数据量可能超过20亿的表准备的,它能提供更靠谱的唯一性保证。regclass:OID的便捷别名regclass这个数据类型挺特别的,它能让咱们直接用对象的名字来引用它的OID,这样一来,查询系统元数据可就方便多了。像表、索引、视图这些对象的标识符,说白了就是系统表sys_class里头oid字段的值。
为了给大家演示regclass怎么用,我先建一个示例表teachers:
CREATE TABLE teachers (
teacher_id INT PRIMARY KEY,
teacher_name VARCHAR(100),
age INT,
sal INT,
gender VARCHAR(10),
title VARCHAR(100),
position VARCHAR(100),
department VARCHAR(100)
);
INSERT INTO teachers (teacher_id, teacher_name, age, sal, gender, title, position, department) VALUES
(1, '张老师', 35, 8000, '男', '教授', '系主任', '计算机科学'),
(2, '李老师', 42, 9500, '女', '副教授', '教研室主任', '软件工程'),
(3, '王老师', 28, 6000, '男', '讲师', '辅导员', '信息管理');
示例:查询sys_class获取表OID
SELECT oid FROM sys_class WHERE relname = 'teachers';
示例:使用regclass转换OID到表名
---查询标识符(OID)为16700的表名
select 16700::regclass;
通过::regclass这个类型转换,咱们就能直接把OID变成对应的对象名称,省事儿!
示例:用regclass简化元数据查询
以前那种老方法,要查表的字段信息,得把sys_attribute和sys_class这两个表关联起来查:
SELECT attrelid,attname,atttypid,attlen, attnum,attnotnull
FROM sys_attribute
WHERE attrelid = (SELECT oid FROM sys_class WHERE relname = 'teachers');
但要是用上regclass类型转换,这个查询就能变得简洁多了:
SELECT
attname,
atttypid,
attlen,
attnum,
attnotnull
FROM
sys_attribute
WHERE
attrelid = 'teachers'::regclass;
这里的'teachers'::regclass实际上跟SELECT oid FROM sys_class WHERE relname='teachers'是一个意思,它返回的就是teachers表的OID,这样一来,元数据查询的写法可就清爽多了。
除了OID,KingbaseES还整了个ROWID机制,专门用来给数据库里每一条记录当“身份证”。ROWID是个伪列,它在逻辑上给每一行数据一个唯一标识,但不像普通列那样实实在在地存在表里头。虽然咱们能查到ROWID的值,但不能直接往里插、改或者删。
KingbaseES提供了多种方式来为表启用ROWID:
GUC参数default_with_rowid:
当此参数设置为true时,所有新创建的表都会自动增加一个名为“ROWID”的隐含列,其类型为ROWID TYPE。所有插入的数据,其ROWID都会被写入到该列中。
示例:通过GUC参数启用ROWID
set default_with_rowid to true; create table tt_rowid_guc(id int); insert into tt_rowid_guc values(10); select rowid,id from tt_rowid_guc;
2. **`CREATE TABLE ... WITH ROWID`语句:**
如果不想全局启用ROWID,可以在创建特定表时使用`WITH ROWID`子句来为其添加ROWID列。
**示例:通过`CREATE TABLE ... WITH ROWID`创建**
```sql
create table student(sno int, name varchar(10), birthday date,
department varchar(10), sex varchar(10))
with rowid;
insert into student values(1, 'li', '2018-1-1', 'physics', 'boy');
insert into student values(5, 'lu', '2018-1-2', 'chinese', 'boy');
select rowid, * from student;
有个地方得提一下,当表创建了ROWID之后,KingbaseES会默认给它建一个带唯一约束的B-tree索引,这样查询性能就能上去。
```sql
d student
3. **`ALTER TABLE ... SET WITH ROWID`语句:**
对于已经存在的表,可以通过`ALTER TABLE`语句来添加ROWID列。
**示例:对已有表添加ROWID**
```sql
create table existing_table(id int);
alter table existing_table set with rowid;
insert into existing_table values(1);
select rowid, id from existing_table;
在KingbaseES里头,default_with_rowid和default_with_oids这俩GUC参数都是管表是不是默认带行标识符的。不过话说回来,它们俩不能同时起作用,default_with_rowid这个参数的优先级比default_with_oids要高。
示例:参数冲突时的行为
-- 检查当前参数设置
show default_with_oids;
show default_with_rowid;
-- 在两个参数都为on的情况下创建表
create table tt_conflict(id int);
insert into tt_conflict values(10);
-- 查询ROWID,成功
select rowid,id from tt_conflict;
-- 查询OID,失败
select oid,id from tt_conflict;
从上述示例可以看出,尽管default_with_oids为on,但由于default_with_rowid也为on,新创建的表tt11会默认带有ROWID,而不会带有OID,这表明ROWID的优先级更高。
还有啊,如果default_with_rowid是false而default_with_oids是true,这时候你还想用CREATE TABLE ... WITH ROWID,系统可是会报错的。
```sql
set default_with_rowid to false;
set default_with_oids to true;
create table tt_explicit_rowid(id int) with rowid;
这个错误信息明确指出,在default_with_rowid为false且default_with_oids为true的情况下,不能显式地创建带有ROWID的表。
KingbaseES的ROWID类型数据表示行记录在数据库中的逻辑唯一标识。它并非物理地址,而是通过内部复合结构存储,并通过64进制字符进行表示。
AAAAAA AAAAAB AAAAAAAAAAAD////// D////// P//////////
超出此范围的ROWID被视为非法格式。示例:ROWID的导入与导出
CREATE TABLE rowid_tt1(id rowid);
-- 导入ROWID
echo `echo 'AAAAAAAAAAABAAAAAAAAAAA' > /tmp/rowid_tt1.txt`
COPY rowid_tt1 FROM '/tmp/rowid_tt1.txt';
select * from rowid_tt1;
-- 导出ROWID
copy rowid_tt1 to '/tmp/rowid_tt1_to.txt';
echo `more /tmp/rowid_tt1_to.txt`
示例:ROWID数据类型校验 ROWID的字符数必须是23个,否则无法正常插入。
insert into rowid_tt1 values('AAAAAAAAAAABAAAAAAAAAAA44444444');
insert into rowid_tt1 values('AAAAAAAAAAABAAAAA');
ROWID支持特定的操作符和使用场景:
SELECT语句中作为投影列,查询当前行的逻辑ID。WHERE子句中使用ROWID进行条件过滤,例如ROWID OP CONST或CONST OP ROWID的形式,其中OP只支持关系运算符(=, >, >=, <, <=, !=等)。ORDER BY和GROUP BY子句中使用ROWID。示例:ROWID的比较操作
select * from rowid_tt1;
select * from rowid_tt1 where id >= 'AAAAAAAAAAABAAAAAAAAAAB';
原则上,后插入的数据的ROWID要大于先插入的ROWID,即ROWID属于单调递增的数据。
在KingbaseES里头,给行加标识符有好几种方法,每种方法设计的时候都有自己特定的目的,适合用在不同场合。咱们要是能把OID、ROWID还有自增主键(比如SERIAL/BIGSERIAL)这仨之间的相同点和不同点搞清楚,对数据库设计和性能优化来说,那可是相当重要的。
WITH OIDS或者default_with_oids给启用了,那它的OID可就只在这一个表里有效了,而且等数到42.9亿左右的时候,还可能转回来重复。regclass类型快速查询对象信息。不推荐作为用户表的业务主键。SERIAL和BIGSERIAL是KingbaseES提供的一种方便创建自增整数列的伪类型,它们实际上是INTEGER和BIGINT类型,并自动关联一个序列(sequence)对象,确保插入新行时自动生成唯一的、递增的值。SERIAL)或8字节(BIGSERIAL)。SERIAL支持20亿条记录,BIGSERIAL支持更大的数据量。为了方便大家对比,我把这仨机制的主要特点整理成下面这个表格:
| 特性 | OID (Object Identifier) | ROWID (Row Identifier) | 自增主键 (SERIAL/BIGSERIAL) |
|---|---|---|---|
| 类型 | 4字节整数 | 64进制字符串表示,内部复合结构(4-18字节) | 4字节整数 (SERIAL) / 8字节整数 (BIGSERIAL) |
| 存储 | 伪列,如果启用则占用存储 | 伪列,逻辑存在,内部机制维护 | 普通列,实际存储在表中 |
| 唯一性 | 系统表全局唯一;用户表局部唯一,可能重复 | 逻辑唯一,默认有唯一索引保证 | 全局唯一,由序列保证 |
| 可见性 | 伪列,需显式查询 | 伪列,需显式查询 | 普通列,直接可见 |
| 可操作性 | 不可直接修改 | 不可直接修改 | 可作为普通列操作(但通常不建议手动修改自增值) |
| 优先级 | 低于ROWID(当GUC参数冲突时) | 高于OID(当GUC参数冲突时) | 独立于OID和ROWID |
| 推荐用途 | 系统内部管理,元数据查询 | 高效行访问,内部行引用 | 用户表业务主键,提供稳定可靠的行标识 |
| 性能 | 查找效率一般,重复风险需额外索引 | 默认有唯一B-tree索引,查找效率高 | 查找效率高,通常作为聚簇索引或主键索引的一部分 |
选自增主键(SERIAL/BIGSERIAL):
SERIAL还是BIGSERIAL,别等到数据溢出了才后悔。选ROWID:
别用OID当用户表的主键:
把这三种行标识机制的特点和适用范围搞明白了,咱们开发者就能根据具体的业务需求和性能考虑,在KingbaseES里做出聪明的选择,从而搭出既高效又稳当的数据库应用。
KingbaseES作为一款关系型数据库,它内部维护着一大堆元数据,专门用来描述数据库对象的结构、属性和它们之间的关系。这些元数据都存放在系统表里头,而OID和regclass类型在咱们访问和解析这些元数据的时候,可是起着关键作用的。
KingbaseES的系统表是数据库的“数据字典”,记录了所有数据库对象的详细信息。以下是一些与对象标识符和元数据查询密切相关的系统表:
sys_class: 存储了所有表、索引、视图等关系对象的元数据。每个关系对象在sys_class中都有一条记录,其oid列就是该对象的唯一标识符。sys_attribute: 存储了所有列(属性)的元数据。每条记录描述了一个表的某个列,通过attrelid(关联关系OID)字段与sys_class中的对象关联。sys_type: 存储了所有数据类型的元数据。sys_proc: 存储了所有函数和存储过程的元数据。regclass进行元数据查询regclass类型是KingbaseES提供的一种便捷机制,它允许用户通过对象的名称来引用其OID,从而简化了对系统表的查询。
示例:查询表的OID
要获取一个表的OID,最直接的方式是查询sys_class表:
SELECT oid FROM sys_class WHERE relname = 'teachers';
使用regclass类型转换,可以更简洁地实现相同目的:
test=# SELECT 'teachers'::regclass;
虽然直接输出的是表名,但其内部表示就是该表的OID。在需要OID作为参数的查询中,这种转换非常有用。
示例:查询表的列信息
假设我们需要查询teachers表的所有列的详细信息,包括列名、数据类型OID、长度、列号等。
传统方式(需要子查询或JOIN):
SELECT attrelid,attname,atttypid,attlen, attnum,attnotnull
FROM sys_attribute
WHERE attrelid = (SELECT oid FROM sys_class WHERE relname = 'teachers');
使用regclass简化查询:
SELECT
attname,
atttypid,
attlen,
attnum,
attnotnull
FROM
sys_attribute
WHERE
attrelid = 'teachers'::regclass;
这里的'teachers'::regclass实际上等同于SELECT oid FROM sys_class WHERE relname='teachers',它返回了teachers表的OID,从而实现了更简洁的元数据查询。
示例:查询数据类型信息 类似地,我们可以查询特定数据类型的OID或名称:
-- 查询integer类型的OID
SELECT 'integer'::regtype;
-- 查询OID为1700的数据类型名称
SELECT 1700::regtype;
regclass机制来监控数据库状态、诊断问题、管理权限和优化性能。所以说,OID和regclass类型是KingbaseES里访问和解析元数据的两把“利器”。它们给咱们提供了一种既高效又灵活的方法,来跟数据库的内部结构打交道,这对于咱们深入理解、有效管理KingbaseES数据库来说,可是至关重要的。
KingbaseES通过GUC(Grand Unified Configuration)参数给了咱们很灵活的配置选项,让咱们能根据具体需求来调整数据库的行为。在处理行标识符这方面,default_with_oids和default_with_rowid是两个挺关键的GUC参数,它们管着新创建的表是不是默认带上OID或者ROWID。搞明白这些参数是怎么交互的、冲突了怎么解决,对于数据库管理员和开发者来说,要想把参数治理好,这可是很重要的。
default_with_oids:
true时,所有新创建的表将默认包含一个OID伪列。false。default_with_rowid:
true时,所有新创建的表将默认包含一个ROWID伪列。false。KingbaseES在处理default_with_oids和default_with_rowid这两个参数的冲突时,遵循明确的优先级规则:
ROWID参数优先级高于OID参数。
这意味着,如果两个参数都被设置为true,KingbaseES会优先为新创建的表启用ROWID,而忽略default_with_oids的设置。
示例:参数冲突时的行为
-- 检查当前参数设置
show default_with_oids;
show default_with_rowid;
-- 在两个参数都为on的情况下创建表
create table tt_conflict(id int);
insert into tt_conflict values(10);
-- 查询ROWID,成功
select rowid,id from tt_conflict;
-- 查询OID,失败
select oid,id from tt_conflict;
从上述示例可以看出,尽管default_with_oids为on,但由于default_with_rowid也为on,新创建的表tt11会默认带有ROWID,而不会带有OID,这表明ROWID的优先级更高。
显式WITH ROWID与GUC参数的交互
当default_with_rowid为false且default_with_oids为true时,如果尝试使用CREATE TABLE ... WITH ROWID语句,KingbaseES会报错。这是因为在当前GUC参数配置下,系统默认会为表创建OID,而用户又显式要求创建ROWID,这与GUC参数的优先级规则相悖。
示例:显式WITH ROWID与GUC参数的冲突
set default_with_rowid to false;
set default_with_oids to true;
create table tt_explicit_rowid(id int) with rowid;
这个错误信息明确指出,在default_with_rowid为false且default_with_oids为true的情况下,不能显式地创建带有ROWID的表。
为了避免不必要的冲突和混淆,建议在KingbaseES中进行参数配置时遵循以下最佳实践:
default_with_oids或default_with_rowid之前,应明确应用程序对行标识符的需求。
default_with_rowid。default_with_oids为false。default_with_rowid的优先级,通常不建议同时将default_with_oids和default_with_rowid都设置为true,以避免潜在的混淆和不确定性。SERIAL或BIGSERIAL作为主键,而不是依赖OID或ROWID。自增主键提供了最可靠的唯一性保证和最佳的业务语义。default_with_rowid保持为false并在创建这些表时显式使用WITH ROWID子句。通过合理的参数治理,可以确保KingbaseES数据库在行标识符方面能够满足应用程序的需求,同时保持数据库的稳定性和可维护性。
咱们今天把KingbaseES里头的OID和ROWID这俩行标识符机制聊了个透,能看出来它们各有各的侧重点,适合用在不同的地方。
OID作为KingbaseES内部的对象标识符,在系统元数据管理这块儿可是个关键角色,它保证了数据库内部对象的一致性和可追溯性。不过对于咱们平时用的业务表来说,因为它“各自为政”的特性和可能重复的风险,一般不建议直接拿它当主键。但话说回来,regclass类型倒是给咱们提供了一种非常方便的查询和管理数据库元数据的方法。
ROWID呢,是KingbaseES专门提供的一种高效的逻辑行标识符。它单调递增的特性,加上默认就有的唯一索引,让它在需要快速定位和操作单行数据的时候特别有用。当然啦,ROWID和OID在GUC参数配置上谁先谁后的问题,也提醒咱们在配数据库的时候得多留个心眼,别踩了不必要的“坑”。
至于咱们最常用的自增主键(SERIAL/BIGSERIAL),那依然是业务表主键的不二之选。它给咱们的保证最稳定、最可靠,也最符合咱们对业务主键的那种直观理解。
总的来说,把这些行标识符的底层实现、特点和适用范围吃透了,对用KingbaseES的伙计们来说特别重要。这不仅能帮咱们更高效地管理和优化数据库,还能在搞应用设计的时候,根据实际需求选出最合适的行标识策略,从而搭出更结实、跑得更快的数据库应用。在KingbaseES的实践里,把这些技术特性灵活运用起来,可是提升数据库整体效能的关键。