写在前面
你有没有想过,数据库里成千上万行数据,它是怎么做到"精准定位"某一行的?
答案就藏在行标识符里。金仓数据库(KingbaseES,下文简称 KES)在这方面有两套方案------一套是 OID ,跟了数据库很多年的"老将";另一套是 ROWID,KES 自己搞出来的"独门武器"。两套东西名字不一样,用法不一样,脾气也不太一样。这篇文章就带你把它们掰开揉碎看清楚。
读完之后,你会明白:
- OID 和 ROWID 到底有什么区别,什么时候该用哪个
- ROWID 背后的编码长什么样,为什么它能做到物理存储
- 三种行标识方案(OID / ROWID / 自增主键)怎么选
- 怎么用 OID 串联系统目录,做高效的元数据查询
- GUC 参数怎么配才不会踩坑
文章目录
-
- 写在前面
- [一、先说 OID------数据库的"身份证号"](#一、先说 OID——数据库的"身份证号")
-
- [OID 是个什么东西](#OID 是个什么东西)
- [系统表里的 OID:全局一把尺](#系统表里的 OID:全局一把尺)
- [普通表里的 OID:各管各的](#普通表里的 OID:各管各的)
- [regclass:OID 的"翻译官"](#regclass:OID 的"翻译官")
- [OID 的几个"坑"](#OID 的几个"坑")
- [二、再说 ROWID------KES 自己的"独门方案"](#二、再说 ROWID——KES 自己的"独门方案")
-
- [ROWID 跟 OID 不一样在哪](#ROWID 跟 OID 不一样在哪)
- [怎么把 ROWID 用起来](#怎么把 ROWID 用起来)
- [ROWID 和 OID 的"互斥"问题](#ROWID 和 OID 的"互斥"问题)
- [三、ROWID 的内部结构------拆开看看](#三、ROWID 的内部结构——拆开看看)
-
- [23 个字符里藏着什么](#23 个字符里藏着什么)
- [实际存储:看起来 23 字符,存起来没那么胖](#实际存储:看起来 23 字符,存起来没那么胖)
- [ROWID 能做比较吗?能](#ROWID 能做比较吗?能)
- [COPY 导入导出也没问题](#COPY 导入导出也没问题)
- [ROWID 使用速查表](#ROWID 使用速查表)
- 四、三套方案摆在一起------OID、ROWID、自增主键怎么选
- [五、用 OID 串联系统目录------元数据查询实战](#五、用 OID 串联系统目录——元数据查询实战)
-
- 系统表之间的关系
- 实战查询:手把手查元数据
- [小贴士:`::` 和 `CAST` 是一回事](#小贴士:
::和CAST是一回事)
- [六、GUC 参数怎么配------生产环境避坑指南](#六、GUC 参数怎么配——生产环境避坑指南)
- 七、收个尾
一、先说 OID------数据库的"身份证号"
OID 是个什么东西
OID,全称 Object Identifier,翻译过来就是"对象标识符"。你可以把它理解成数据库内部的一张身份证------每张表、每个视图、每个索引、每个函数,在系统里都有一个 OID 和它一一对应。
它本质上是一个 4 字节的无符号整数,系统表里默认就带着它,不需要你操心。
不过有个细节要注意:你平时用 \d 看表结构的时候,OID 是不显示的。普通用户表在创建时也默认不带 OID------得你主动要,它才会给。
系统表里的 OID:全局一把尺
系统表中的 OID 是"全局通用的"。什么意思呢?同一个数据库集群里,不管你查的是 sys_type、sys_proc 还是 sys_class,OID 都不会撞车------它是一个集群级的全局编号。
来看个实际例子:
sql
-- 看看 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 是不是会冲突:
sql
-- 创建一个复合类型
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)
看到了吗?aatyp 拿到 55136,func_test05 拿到 55137,它俩压根不在同一张系统表里,但 OID 依然错开了一位,没有任何冲突。这就是"全局"的含义。
普通表里的 OID:各管各的
但到了普通表这里,画风就变了。
普通表的 OID 是局部的------每张表从 1 开始自己数,表 A 的 OID=1 和表 B 的 OID=1 完全不是一回事。
默认建表是不带 OID 的,你硬查会报错:
sql
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,你得主动开口。有两种方式:
方法一:打开全局开关
sql
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)
方法二:建表时直接声明
sql
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)
tt6 从 1 开始,tt7 也从 1 开始,各走各的路,互不干涉。
regclass:OID 的"翻译官"
说了这么多 OID 的编号,你可能会有一个疑问:我知道一张表叫 teachers,但我怎么知道它的 OID 是多少?
这时候 regclass 类型就派上用场了。它是 OID 的"别名类型",能自动帮你把表名翻译成 OID,省得你到处查。
sql
-- 直接把 OID 翻译成表名
test=# SELECT 16700::regclass;
regclass
----------
teachers
(1 row)
来看一个真实的查询场景:我想知道 teachers 表里有哪些字段。
不用 regclass,你得写子查询:
sql
SELECT attrelid, attname, atttypid, attlen, attnum, attnotnull
FROM sys_attribute
WHERE attrelid = (SELECT oid FROM sys_class WHERE relname = 'teachers');
用了 regclass,一行就搞定:
sql
SELECT attrelid, attname, atttypid, attlen, attnum, attnotnull
FROM sys_attribute
WHERE attrelid = 'teachers'::regclass;
输出结果一模一样:
attrelid | attname | atttypid | attlen | attnum | attnotnull
----------+-------------+----------+--------+--------+------------
16700 | tableoid | 26 | 4 | -6 | t
16700 | cmax | 29 | 4 | -5 | t
16700 | xmax | 28 | 4 | -4 | t
16700 | cmin | 29 | 4 | -3 | t
16700 | xmin | 28 | 4 | -2 | t
16700 | ctid | 27 | 6 | -1 | t
16700 | teacher_id | 17000 | -1 | 1 | t
16700 | teacher_name| 1043 | -1 | 2 | t
16700 | age | 17000 | -1 | 3 | f
16700 | sal | 17000 | -1 | 4 | f
16700 | gender | 1043 | -1 | 5 | f
16700 | title | 1043 | -1 | 6 | f
16700 | position | 1043 | -1 | 7 | f
16700 | department | 1043 | -1 | 8 | t
(14 rows)
'teachers'::regclass 背后干的事情,其实就是帮你跑了一遍 SELECT oid FROM sys_class WHERE relname = 'teachers',只不过写起来更利索。
OID 的几个"坑"
用到 OID 的时候,有四件事要心里有数:
| 事项 | 说明 |
|---|---|
| 系统表全局 vs 普通表局部 | 系统表的 OID 集群内不会重复;普通表每张表从 1 开始,互相独立 |
| 4 字节有天花板 | OID 是 4 字节无符号整数,上限大约 42.9 亿。数据量特别大的表可能会用完,一旦用完就会出现回卷重复 |
| 没有唯一性兜底 | WITH OIDS 不会自动建唯一索引,不能指望它来保证行级唯一 |
| 老老实实用自增列 | 需要唯一行标识的场合,用 SERIAL 或 BIGSERIAL 才是正道 |
二、再说 ROWID------KES 自己的"独门方案"
ROWID 跟 OID 不一样在哪
如果说 OID 是数据库的"内部编号",那 ROWID 更像是每一行数据的"家庭住址"------它不仅标记了你是谁,还标记了你在哪。
ROWID 是 KES 独有的行标识机制,有三个跟 OID 不一样的关键点:
- 真实存储:ROWID 不是虚拟的,它物理存在于表中,以隐藏列的方式存储
- 自动建索引:启用 ROWID 后,KES 会自动为它创建一个 B-tree 唯一索引
- 支持范围查询 :因为有了索引和排序规则,ROWID 可以做
>、<这类范围过滤
简单说,OID 更像是"查户口"用的,ROWID 则是"找人"用的。
怎么把 ROWID 用起来
KES 给了三种方式,从简单到灵活都有。
第一种:打开全局参数(最省事)
把 default_with_rowid 设成 true,之后所有新建的表都会自动带上 ROWID:
sql
test=# SHOW default_with_oids;
default_with_oids
-------------------
on
(1 row)
test=# SHOW default_with_rowid;
default_with_rowid
--------------------
on
(1 row)
test=# CREATE TABLE tt11(id int);
CREATE TABLE
test=# INSERT INTO tt11 VALUES(10);
INSERT 0 1
-- ROWID 列直接可用
test=# SELECT rowid, id FROM tt11;
rowid | id
-----------------------------+----
AAAAAAAAAADQCAAAAAAAAAAA | 10
(1 row)
-- 但 OID 列不存在------ROWID 的优先级比 OID 高
test=# SELECT oid, id FROM tt11;
ERROR: column "oid" does not exist
这里有个重要细节:当 default_with_oids 和 default_with_rowid 同时为 true 时,ROWID 会胜出。最终创建的表只带 ROWID,不带 OID。
第二种:建表时直接指定(最明确)
sql
test=# CREATE TABLE student (
sno int,
name varchar(10),
birthday date,
department varchar(10),
sex varchar(10)
) WITH ROWID;
CREATE TABLE
test=# INSERT INTO student VALUES
(1, 'li', '2018-1-1', 'physics', 'boy'),
(5, 'lu', '2018-1-2', 'chinese', 'boy'),
(3, 'wang', '2018-1-3', 'english', 'girl'),
(4, 'zhang', '2018-1-4', 'history', 'boy'),
(2, 'jack', '2018-1-5', 'history', 'boy');
test=# SELECT rowid, * FROM student;
rowid | sno | name | birthday | department | sex
-----------------------------+-----+-------+---------------------+------------+------
AAAAAAAAAADP5AAAAAAAAAAA | 1 | li | 2018-01-01 00:00:00 | physics | boy
AAAAAAAAAADP5AAAAAAAAAAB | 5 | lu | 2018-01-02 00:00:00 | chinese | boy
AAAAAAAAAADP5AAAAAAAAAAC | 3 | wang | 2018-01-03 00:00:00 | english | girl
AAAAAAAAAADP5AAAAAAAAAAD | 4 | zhang | 2018-01-04 00:00:00 | history | boy
AAAAAAAAAADP5AAAAAAAAAAE | 2 | jack | 2018-01-05 00:00:00 | history | boy
(5 rows)
用 \d 看看表结构,你会发现 KES 已经帮你把索引建好了:
test=# \d student
Table "public.student"
Column | Type | Collation | Nullable | Default
-----------+-----------------------+-----------+----------+---------
sno | integer | | |
name | character varying(10) | | |
birthday | date | | |
department| character varying(10) | | |
sex | character varying(10) | | |
Indexes:
"student_rowid_key" UNIQUE CONSTRAINT, btree ("rowid")
看到了吧?student_rowid_key,B-tree 唯一索引,自动就位,不用你操心。
第三种:给已有表补上 ROWID(最灵活)
表已经建好了、数据也在了,想加 ROWID?没问题:
sql
-- 直接用 ALTER 就行,不影响已有数据
test=# ALTER TABLE t_row SET WITH ROWID;
-- 验证一下
test=# SELECT rowid, * FROM t_row LIMIT 3;
rowid | id | name
-----------------------------+----+------
AAAAAAAAAADP5AAAAAAAAAAA | 1 | test1
AAAAAAAAAADP5AAAAAAAAAAB | 2 | test2
AAAAAAAAAADP5AAAAAAAAAAC | 3 | test3
(3 rows)
执行 ALTER TABLE ... SET WITH ROWID 时,KES 会在表中新增一个名为 rowidtype 的隐藏列,同时把现有行的 ROWID 值回填进去,并自动创建唯一索引。
ROWID 和 OID 的"互斥"问题
这两位不是"想同时用就能同时用"的。一个经典冲突场景:
sql
-- 关掉 ROWID,打开 OID
test=# SET default_with_rowid TO false;
SET
test=# SET default_with_oids TO true;
SET
-- 然后尝试创建带 ROWID 的表------直接报错
test=# CREATE TABLE tt16(id int) WITH ROWID;
ERROR: cannot create table with rowid
(default_with_rowid is false, and default_with_oids is true)!
原因很简单:KES 内部做了互斥处理,一张表不能同时拥有 OID 列和 ROWID 列。这个报错信息已经说得很明白了------你想用 ROWID,但全局参数把 ROWID 关了又把 OID 开了,这就矛盾了。
三、ROWID 的内部结构------拆开看看
23 个字符里藏着什么
ROWID 在 KES 里是一种独立的数据类型(叫 rowidtype),长得像一串随机字符串,比如 AAAAAAAAADP5AAAAAAAAAAB。实际上它用 Base64 编码 (字符集 A-Z、a-z、0-9、+、/),固定 23 个字符长。
这 23 个字符分三段,每一段都有明确的含义:
AAAAAA AAAAAA BAAAAAAAAAAA
────── ────── ────────────
块号 事务ID 命令计数器
[0~5] [6~11] [12~22]
32位 32位 64位
| 段 | 字符位置 | 位数 | 干什么用的 |
|---|---|---|---|
| 存储位置(块号) | 第 1~6 字符 | 32 位 | 这行数据存在哪个存储块 |
| 事务 ID(xid) | 第 7~12 字符 | 32 位 | 插入这行数据的事务编号 |
| 命令计数器 | 第 13~23 字符 | 64 位 | 同一个事务里第几条命令 |
取值范围:
| 块号 | 事务 ID | 命令计数器 | |
|---|---|---|---|
| 最小值 | AAAAAA | AAAAAA | BAAAAAAAAAAA |
| 最大值 | D////// | D////// | P////////// |
有个小细节:每次开始一个新事务(包括执行匿名块),命令计数器都会重新计算。所以如果你看到两行的前 12 个字符一样、后 11 个字符重新从 BAAAAAAAAAAA 开始,多半是同一事务里的操作。
实际存储:看起来 23 字符,存起来没那么胖
虽然 ROWID 显示出来固定 23 个字符,但 KES 内部用的是变长编码 ,实际物理存储只需要 4 到 18 个字节。对于行标识这种高频使用的信息,这个开销算是相当克制了。
ROWID 能做比较吗?能
ROWID 不只是能查,还能比较。支持的操作符有:=、!=、>、>=、<、<=。
比较规则也不复杂:
- 等于 / 不等于:逐位比对全部信息
- 大于 / 小于:先比块号(高位),块号相同再比事务 ID,再相同就比命令计数器
sql
-- 准备测试数据
CREATE TABLE rowid_tt1(id rowid);
-- 插入几条 ROWID 值
COPY rowid_tt1 FROM '/tmp/rowid_tt1.txt';
test=# SELECT * FROM rowid_tt1;
id
-------------------------
AAAAAAAAAAABAAAAAAAAAAA
AAAAAAAAAAABAAAAAAAAAAB
AAAAAAAAAAABAAAAAAAAAAC
(3 rows)
-- 范围查询:找出大于等于第二条的记录
test=# SELECT * FROM rowid_tt1 WHERE id >= 'AAAAAAAAAAABAAAAAAAAAAB';
id
-------------------------
AAAAAAAAAAABAAAAAAAAAAB
AAAAAAAAAAABAAAAAAAAAAC
(2 rows)
COPY 导入导出也没问题
ROWID 类型完全支持标准的 COPY 操作:
sql
-- 从文件导入 ROWID 数据
COPY rowid_tt1 FROM '/tmp/rowid_tt1.txt';
-- 导出后再比对,数据完全一致
COPY rowid_tt1 TO '/tmp/rowid_tt1_to.txt';
到操作系统层面验证:
bash
[root@localhost tmp]# cat rowid_tt1.txt
AAAAAAAAAAABAAAAAAAAAAA
[root@localhost tmp]# cat rowid_tt1_to.txt
AAAAAAAAAAABAAAAAAAAAAA
一模一样,没有任何信息丢失。
不过要注意格式校验------ROWID 必须严格是 23 个字符,多一个少一个都不行:
sql
-- 太长了,报错
test=# INSERT INTO rowid_tt1 VALUES('AAAAAAAAAAABAAAAAAAAAAA44444444');
ERROR: invalid input syntax for type rowid: "AAAAAAAAAAABAAAAAAAAAAA44444444"
-- 太短了,也报错
test=# INSERT INTO rowid_tt1 VALUES('AAAAAAAAAAABAAAAA');
ERROR: invalid input syntax for type rowid: "AAAAAAAAAAABAAAAA"
ROWID 使用速查表
| 能不能 | 具体说明 |
|---|---|
| SELECT 里用 ROWID | 可以,直接 SELECT rowid, * FROM 表名 |
| WHERE 里用 ROWID 过滤 | 可以,支持 ROWID > 常量 这种写法 |
| ORDER BY / GROUP BY | 可以,ROWID 有排序规则 |
| 存储过程里引用 | 可以,用数字 1, 2, 3 或字符串 '1', '2', '3' 都行 |
| ROWID 之间做运算 | 不行,ROWID 不支持加减乘除 |
| 建索引 | 支持 B-tree 和 HASH 两种索引类型 |
四、三套方案摆在一起------OID、ROWID、自增主键怎么选
前面把 OID 和 ROWID 各自的用法都过了一遍,但实际干活的时候你还得做一个选择:这三种方案,到底该用哪个?
先看对比:
| OID | ROWID | SERIAL / BIGSERIAL | |
|---|---|---|---|
| 是什么 | 系统伪列,可选开启 | 物理存储的隐藏列 | 用户自己定义的显式列 |
| 数据类型 | 4 字节无符号整数 | Base64 字符串(rowidtype) | 整数(int4 或 int8) |
| 唯一范围 | 系统表全局唯一;普通表仅表内 | 仅表内 | 仅表内 |
| 物理存储 | 元组头部,不占用户列空间 | 隐藏列,变长 4~18 字节 | 用户列,固定 4 或 8 字节 |
| 索引 | 无自动索引 | 自动建 B-tree 唯一索引 | 需要手动建主键或唯一约束 |
| 上限 | ~42.9 亿(有回卷风险) | 编码空间远大于 OID | SERIAL ~21 亿;BIGSERIAL ~9.2×10^18 |
| 适合干什么 | 系统目录查询 | 行级定位、快速查找 | 业务主键、外键关联 |
| 参数控制 | default_with_oids |
default_with_rowid |
不需要 GUC 参数 |
怎么选?一句话版:
- 查系统目录、做 DBA 运维 → OID + regclass
- 要快速定位某一行、调试数据 → ROWID
- 业务需要唯一标识 → SERIAL / BIGSERIAL + PRIMARY KEY
下面用一段完整的代码,把三种方案在同一张表上的表现都演示出来:
sql
-- ========== 方案一:OID ==========
-- 开启 OID 参数后建表
SET default_with_oids TO true;
CREATE TABLE demo_oid (
id int,
name varchar(20)
);
INSERT INTO demo_oid VALUES (1, 'alice'), (2, 'bob');
-- OID 从 1 开始自增,但注意:没有唯一索引保障
test=# SELECT oid, id, name FROM demo_oid;
oid | id | name
-----+----+-------
1 | 1 | alice
2 | 2 | bob
(2 rows)
-- ========== 方案二:ROWID ==========
SET default_with_rowid TO true;
SET default_with_oids TO false;
CREATE TABLE demo_rowid (
id int,
name varchar(20)
) WITH ROWID;
INSERT INTO demo_rowid VALUES (1, 'alice'), (2, 'bob');
-- ROWID 自动生成,自带唯一索引
test=# SELECT rowid, id, name FROM demo_rowid;
rowid | id | name
-----------------------------+----+-------
AAAAAAAAAADP5AAAAAAAAAAA | 1 | alice
AAAAAAAAAADP5AAAAAAAAAAB | 2 | bob
(2 rows)
-- ========== 方案三:自增主键(推荐业务场景) ==========
CREATE TABLE demo_serial (
id BIGSERIAL PRIMARY KEY,
name varchar(20)
);
INSERT INTO demo_serial (name) VALUES ('alice'), ('bob');
-- id 由序列自动分配,主键约束保证唯一
test=# SELECT id, name FROM demo_serial;
id | name
----+-------
1 | alice
2 | bob
(2 rows)
五、用 OID 串联系统目录------元数据查询实战
KES 的系统目录(System Catalog)是整个数据库运行的基础设施。在这些目录表中,OID 扮演着"粘合剂"的角色------不同的系统表通过 OID 互相关联,形成了完整的元数据网络。
系统表之间的关系
先看一张关系图,搞清楚核心系统表是怎么连起来的:
┌──────────────┐
│ sys_class │ ← 所有"关系"(表/索引/视图)的注册表
│ │
│ oid ←───── │──→ sys_type.oid (行类型)
│ reltype │
│ relam ─────│──→ sys_am.oid (访问方法)
└──────┬───────┘
│
oid 被引用
│
▼
┌────────────────────┐
│ sys_attribute │ ← 每张表的每个列都在这里有一条记录
│ │
│ attrelid ──────── │──→ sys_class.oid (这列属于哪张表)
│ atttypid ──────── │──→ sys_type.oid (这列是什么类型)
└────────────────────┘
记住这条主线:sys_class (表本身)→ sys_attribute (表的列)→ sys_type(列的类型)。OID 就是把它们串起来的那根线。
实战查询:手把手查元数据
场景一:查一张表有哪些列
sql
SELECT a.attname AS column_name,
t.typname AS data_type,
a.attlen AS type_length,
a.attnum AS column_order,
a.attnotnull AS not_null
FROM sys_attribute a
JOIN sys_type t ON a.atttypid = t.oid
WHERE a.attrelid = 'teachers'::regclass -- regclass 直接翻译表名
AND a.attnum > 0 -- 过滤掉系统隐藏列
ORDER BY a.attnum;
场景二:查一张表的索引信息
sql
SELECT c.relname AS index_name,
am.amname AS index_type
FROM sys_index i
JOIN sys_class c ON i.indexrelid = c.oid -- 索引对象
JOIN sys_am am ON c.relam = am.oid -- 访问方法
WHERE i.indrelid = 'teachers'::regclass; -- 被索引的表
场景三:查数据库里有哪些自定义类型
sql
SELECT oid, typname, typtype
FROM sys_type
WHERE typname NOT LIKE 'sys_%'
ORDER BY oid;
场景四:查某张表上绑定了哪些触发器
sql
SELECT t.tgname AS trigger_name,
c.relname AS table_name,
p.proname AS function_name,
CASE t.ttype
WHEN 'a' THEN 'AFTER'
WHEN 'b' THEN 'BEFORE'
ELSE 'OTHER'
END AS trigger_timing
FROM sys_trigger t
JOIN sys_class c ON t.tgrelid = c.oid
JOIN sys_proc p ON t.tgfoid = p.oid
WHERE c.relname = 'teachers';
小贴士::: 和 CAST 是一回事
在 KES 里做类型转换,有两种写法,效果完全一样:
sql
-- 写法一:双冒号(简洁,KES 风格)
test=# SELECT '25'::integer, sys_typeof('25'::integer),
'12-oct-2023'::date, sys_typeof('12-oct-2023'::date);
int4 | sys_typeof | date | sys_typeof
------+------------+---------------------+------------
25 | integer | 2023-10-12 00:00:00 | date
(1 row)
-- 写法二:CAST(标准 SQL,跨数据库通用)
test=# SELECT CAST('25' AS integer),
CAST('12-oct-2023' AS date);
int4 | date
------+---------------------
25 | 2023-10-12 00:00:00
(1 row)
自己用、写脚本,:: 更省事;写需要跨数据库兼容的代码,用 CAST 更稳。
六、GUC 参数怎么配------生产环境避坑指南
GUC(Grand Unified Configuration)是 KES 的参数配置体系。跟 OID 和 ROWID 相关的参数就两个,但配不好就容易踩雷。
两个参数
| 参数 | 类型 | 默认值 | 作用 |
|---|---|---|---|
default_with_oids |
BOOL | false | 新建表是否默认带 OID 列 |
default_with_rowid |
BOOL | false | 新建表是否默认带 ROWID 列 |
四种组合的建表行为
default_with_oids |
default_with_rowid |
建出来的表 |
|---|---|---|
| false | false | 什么都没有------普通表 |
| true | false | 带 OID,没有 ROWID |
| false | true | 带 ROWID,没有 OID |
| true | true | ROWID 赢------带 ROWID,没有 OID |
唯一会报错的组合
sql
test=# SET default_with_rowid TO false;
SET
test=# SET default_with_oids TO true;
SET
test=# CREATE TABLE bad_idea(id int) WITH ROWID;
ERROR: cannot create table with rowid
(default_with_rowid is false, and default_with_oids is true)!
错误信息已经很直白了:你全局关了 ROWID 又开了 OID,然后还想建带 ROWID 的表------不好意思,不行。
生产环境五条建议
1. 集群级别统一配置
不要让不同会话的参数值不一样,否则同样的建表语句跑出不同的结果,排查起来非常痛苦。建议在 kingbase.conf 中统一设置:
ini
# kingbase.conf
default_with_oids = false
default_with_rowid = true
2. 需要行标识?优先 ROWID
ROWID 有物理存储、有自动索引、支持范围查询------这些都是 OID 做不到的。除非你只做系统目录查询,否则 ROWID 是更好的选择。
3. DDL 里写清楚,别依赖全局参数
sql
-- 推荐:显式声明,一眼就知道这张表带了什么
CREATE TABLE orders (
order_id BIGSERIAL PRIMARY KEY,
amount numeric(10,2),
status varchar(20)
) WITH ROWID;
-- 不推荐:依赖全局参数,换台机器可能行为就不一样了
CREATE TABLE orders (
order_id BIGSERIAL PRIMARY KEY,
amount numeric(10,2),
status varchar(20)
);
4. 不要在同一数据库里混用 OID 和 ROWID
混着用只会增加运维的心智负担,而且一旦参数配置不当就会遇到上面说的报错。选一种,全库统一。
5. 不管用 OID 还是 ROWID,业务主键都不能省
sql
-- 正确做法:ROWID 做行定位,BIGSERIAL + PRIMARY KEY 做业务标识
CREATE TABLE products (
product_id BIGSERIAL PRIMARY KEY,
product_name varchar(100) NOT NULL,
price numeric(10,2)
) WITH ROWID;
OID 和 ROWID 是数据库内部的技术手段,业务层面的唯一性还是得靠主键约束来保障。
七、收个尾
到这,OID 和 ROWID 的底牌基本都亮出来了。简单回顾一下:
OID 是 KES 系统内部的"编号系统",在系统表里全局唯一,配合
regclass做元数据查询非常顺手。但它有 4 字节的天花板,普通表里又是局部的,不适合做业务主键。ROWID 是 KES 自己搞出来的行标识方案------物理存储、自动建唯一索引、支持范围查询和排>序分组,定位一行数据比 OID 精准得多。
SERIAL / BIGSERIAL 则是业务主键的标准选择,配合 PRIMARY KEY 约束,适合绝大多数应>用场景。
三者各管一段,搞清楚谁该在什么场合出场,你就算是把金仓数据库的行标识机制真正吃透了。