文章目录
-
- [一、OID 概述](#一、OID 概述)
-
- [1.1 什么是 OID?------基本定义与特性](#1.1 什么是 OID?——基本定义与特性)
- [1.2 核心特性](#1.2 核心特性)
- [1.3 OID 的现代替代方案](#1.3 OID 的现代替代方案)
- [1.4 OID 的真实定位](#1.4 OID 的真实定位)
- [1.5 实践建议](#1.5 实践建议)
- [二、OID 的历史演进:从默认启用到逐步弃用](#二、OID 的历史演进:从默认启用到逐步弃用)
-
- [2.1 PostgreSQL 早期(< 8.0)](#2.1 PostgreSQL 早期(< 8.0))
- [2.2 PostgreSQL 8.0(2005 年)重大变更](#2.2 PostgreSQL 8.0(2005 年)重大变更)
- [2.3 PostgreSQL 12(2019 年)彻底移除](#2.3 PostgreSQL 12(2019 年)彻底移除)
- [三、OID 在系统内部的核心用途](#三、OID 在系统内部的核心用途)
-
- [3.1 系统目录(System Catalogs)的主键](#3.1 系统目录(System Catalogs)的主键)
- [3.2 类型系统(Type System)的核心](#3.2 类型系统(Type System)的核心)
- [3.3 函数与过程的唯一标识](#3.3 函数与过程的唯一标识)
- [3.4 大对象(Large Objects, LO)的地址](#3.4 大对象(Large Objects, LO)的地址)
- [3.5 内部缓存与哈希表的键](#3.5 内部缓存与哈希表的键)
- [四、OID 的分配机制与持久化](#四、OID 的分配机制与持久化)
-
- [4.1 分配器:`nextOid` 与 `oidCount`](#4.1 分配器:
nextOid与oidCount) - [4.2 持久化位置](#4.2 持久化位置)
- [4.1 分配器:`nextOid` 与 `oidCount`](#4.1 分配器:
- [五、OID 的安全与设计争议](#五、OID 的安全与设计争议)
-
- [5.1 为什么弃用用户表 OID?](#5.1 为什么弃用用户表 OID?)
- [5.2 OID 是否会耗尽?](#5.2 OID 是否会耗尽?)
- [六、实用技巧:如何与 OID 交互?](#六、实用技巧:如何与 OID 交互?)
-
- [6.1 查看对象 OID](#6.1 查看对象 OID)
- [6.2 通过 OID 反查对象名](#6.2 通过 OID 反查对象名)
- [6.3 监控 OID 使用情况](#6.3 监控 OID 使用情况)
- [七、深入源码:OID 在 C 层的表示](#七、深入源码:OID 在 C 层的表示)
在 PostgreSQL 的内核设计中,OID(Object Identifier) 是一个贯穿始终、历史悠久但又常被误解的核心概念。它既是系统表的"身份证",也是早期用户数据的隐式主键;既支撑着类型系统、函数分发、大对象存储等关键功能,又因安全性和可移植性问题在现代版本中逐步退居幕后。
本文将从 历史演进、内部结构、使用场景、安全风险、替代方案 五个维度,全面剖析 OID 在 PostgreSQL 中的真实角色与工作机制。
一、OID 概述
1.1 什么是 OID?------基本定义与特性
- OID(Object Identifier) 是 PostgreSQL 内部用于唯一标识数据库对象 的 32 位无符号整数(
uint32)。 - 取值范围:
0到2^32 - 1(即 0 ~ 4,294,967,295) - 特殊保留值:
InvalidOid = 0:表示无效或未设置BootstrapObjectId = 1:引导阶段对象FirstNormalObjectId = 16384:普通用户对象起始 ID
1.2 核心特性
| 特性 | 说明 |
|---|---|
| 全局唯一(在单个集群内) | 所有数据库共享同一 OID 空间(部分对象如表、函数) |
| 单调递增分配 | 由共享内存中的计数器(nextOid)分配,重启不重置 |
| 不可变 | 一旦分配,对象的 OID 永不改变(即使重命名、移动 schema) |
| 系统级标识 | 主要用于系统目录(pg_class, pg_proc 等)内部引用 |
注意:OID ≠ 行 ID(ctid),也 ≠ 用户主键。它是元数据层面的对象句柄。
1.3 OID 的现代替代方案
虽然 OID 退居幕后,但其功能已被更安全、灵活的机制替代:
| 场景 | 旧方式(OID) | 新方式 |
|---|---|---|
| 行唯一标识 | oid 列 |
SERIAL / IDENTITY / UUID |
| 对象引用 | 直接存 OID | 使用 regclass, regproc, regtype 等注册类类型 |
| 大对象 | oid 句柄 |
仍用 OID,但建议用 bytea 或外部存储替代 LO |
| 元数据查询 | SELECT * FROM pg_class WHERE oid = 123 |
SELECT * FROM pg_class WHERE relname = 'xxx'::regclass |
推荐使用
regclass安全引用表:
sql-- 自动解析表名到 OID,且检查存在性 SELECT reltuples FROM pg_class WHERE oid = 'users'::regclass;
1.4 OID 的真实定位
| 维度 | 结论 |
|---|---|
| 是否过时? | 对系统内部而言,仍是核心机制 |
| 用户是否需关心? | 一般不需要,但理解有助于排查问题 |
| 能否禁用? | 不能,系统依赖 OID 运行 |
| 是否安全? | 系统内部使用安全;用户表已禁用 |
| 未来会消失吗? | 不会,但使用范围将更局限于内核 |
OID 是 PostgreSQL 元数据系统的"脊椎" ------ 虽不可见,却支撑着整个数据库的结构与行为。
1.5 实践建议
- 不要尝试在用户表中模拟 OID → 使用
BIGSERIAL或UUID - 查询系统表时优先用名称 +
::regclass→ 更安全、可读 - 避免硬编码 OID → 不同环境 OID 不同,会导致脚本失效
- 大对象谨慎使用 → 考虑
bytea或应用层存储 - 监控系统对象增长 → 虽然 OID 耗尽概率极低,但异常增长可能预示问题
二、OID 的历史演进:从默认启用到逐步弃用
2.1 PostgreSQL 早期(< 8.0)
- 所有用户表自动包含隐藏列
oid - 每行数据都有一个 OID,可直接通过
SELECT * FROM table WHERE oid = 12345 - 支持
oid作为行标识,用于快速定位
2.2 PostgreSQL 8.0(2005 年)重大变更
-
默认不再为用户表添加
oid列 -
需显式指定
WITH OIDS才启用:sqlCREATE TABLE users (id SERIAL, name TEXT) WITH OIDS; -
原因:
- 安全风险(可通过 OID 推测数据分布)
- 存储开销(每行多 4 字节)
- 复制与逻辑备份兼容性问题
2.3 PostgreSQL 12(2019 年)彻底移除
-
完全废弃
WITH OIDS语法 -
尝试创建带 OID 的表会报错:
ERROR: tables declared WITH OIDS are not supported -
用户表彻底无法拥有 OID 列
结论:现代 PostgreSQL 中,OID 仅用于系统内部对象,不再暴露给用户表数据。
三、OID 在系统内部的核心用途
尽管用户表不再使用 OID,但它在 PostgreSQL 内核中依然扮演着不可替代的角色。
3.1 系统目录(System Catalogs)的主键
PostgreSQL 的元数据存储在一系列系统表中,这些表的主键几乎全是 OID:
| 系统表 | 描述 | OID 字段 |
|---|---|---|
pg_class |
表、索引、序列、视图等 | oid |
pg_proc |
函数/过程 | oid |
pg_type |
数据类型 | oid |
pg_namespace |
Schema | oid |
pg_operator |
操作符 | oid |
pg_am |
访问方法(如 btree, hash) | oid |
例如:
sql
SELECT oid, relname FROM pg_class WHERE relname = 'users';
-- 返回:16389 | users
其他系统表通过 OID 引用这些对象:
sql
-- pg_index.indrelid 引用 pg_class.oid(被索引的表)
-- pg_depend.refobjid 引用任意系统对象的 OID
OID 是系统表之间的"外键"纽带,构成完整的依赖图谱。
3.2 类型系统(Type System)的核心
PostgreSQL 是强类型数据库,每个值都有明确类型,而类型由 pg_type.oid 标识。
int4的 OID 是23text的 OID 是25- 自定义类型(如
CREATE TYPE status AS ENUM (...))会分配新 OID
函数重载、操作符选择、隐式转换等都依赖 OID 进行匹配:
c
// C 函数中常见
Datum myfunc(PG_FUNCTION_ARGS) {
Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
if (argtype == TEXTOID) { ... }
}
💡 你可以通过
regtype查看类型 OID:
sqlSELECT 'text'::regtype::oid; -- 返回 25
3.3 函数与过程的唯一标识
每个函数(包括重载版本)在 pg_proc 中有唯一 OID:
sql
SELECT oid, proname, proargtypes
FROM pg_proc
WHERE proname = 'upper';
即使函数名相同,只要参数类型不同,OID 就不同。执行计划、缓存、权限控制均基于 OID。
3.4 大对象(Large Objects, LO)的地址
PostgreSQL 的 大对象 API (lo_create, lo_open, lo_read 等)使用 OID 作为大对象的句柄:
sql
-- 创建一个大对象,返回其 OID
SELECT lo_create(0); -- 如返回 16390
-- 后续通过该 OID 读写
SELECT lo_get(16390);
大对象实际存储在系统表 pg_largeobject 中,以 loid(即 OID)为分区键。
⚠️ 注意:大对象 OID 与表/函数 OID 共享同一命名空间!
3.5 内部缓存与哈希表的键
PostgreSQL 的 SysCache(系统缓存)使用 OID 作为哈希键,加速元数据查找:
SysCacheGetAttr(RELOID, HeapTuple, ...)通过表 OID 快速获取属性- 函数、类型、操作符等均有对应缓存
这使得解析 SELECT * FROM users 时,能快速通过 relname → oid → reltup 完成绑定。
四、OID 的分配机制与持久化
4.1 分配器:nextOid 与 oidCount
- 全局变量
nextOid存储下一个可用 OID oidCount记录本次启动已分配数量- 每次分配后
nextOid++ - 定期写入 WAL (通过
XLOG_STORE_OIDS记录),确保崩溃恢复后不重复
OID 分配是集群级全局行为,跨数据库共享。
4.2 持久化位置
- OID 本身存储在各系统表的
oid列中(如pg_class.oid) - 系统表是普通 heap 表,OID 作为普通字段存储
- 但因其为主键,通常有唯一索引(如
pg_class_oid_index)
五、OID 的安全与设计争议
5.1 为什么弃用用户表 OID?
| 问题 | 说明 |
|---|---|
| 信息泄露 | OID 单调递增,攻击者可推测数据量、插入时间 |
| 复制冲突 | 逻辑复制或主从切换时,OID 可能重复 |
| 存储浪费 | 每行多 4 字节,对宽表影响显著 |
| 非必要依赖 | 用户应使用显式主键(如 SERIAL / UUID) |
5.2 OID 是否会耗尽?
- 理论上会(42 亿上限),但实际几乎不可能 :
- 系统对象增长极慢(每天新增几百个已算高频)
- 即使每秒创建 100 个对象,也需要 1360 年 耗尽
- 若真耗尽,PostgreSQL 会报错并拒绝创建新对象(类似 XID 回卷,但概率极低)
六、实用技巧:如何与 OID 交互?
6.1 查看对象 OID
sql
-- 表
SELECT 'users'::regclass::oid;
-- 函数
SELECT 'upper(text)'::regprocedure::oid;
-- 类型
SELECT 'jsonb'::regtype::oid;
6.2 通过 OID 反查对象名
sql
-- 表名
SELECT relname FROM pg_class WHERE oid = 16389;
-- 函数名
SELECT proname FROM pg_proc WHERE oid = 12345;
-- 使用系统函数(更安全)
SELECT pg_describe_object('pg_class'::regclass, 16389, 0);
-- 返回:table users
6.3 监控 OID 使用情况
sql
-- 当前最大 OID(近似)
SELECT max(oid) FROM pg_class;
-- 估算剩余空间
SELECT (2^32 - max(oid)) AS remaining_oids
FROM (
SELECT max(oid) FROM pg_class
UNION ALL
SELECT max(oid) FROM pg_proc
UNION ALL
SELECT max(oid) FROM pg_type
) t;
七、深入源码:OID 在 C 层的表示
在 PostgreSQL 源码中,OID 被定义为:
c
// src/include/postgres_ext.h
typedef unsigned int Oid;
常用常量:
c
#define InvalidOid ((Oid) 0)
#define FirstNormalObjectId ((Oid) 16384)
函数示例(分配 OID):
c
// src/backend/utils/cache/inval.c
Oid GetNewOid(Relation relation) {
Oid result = InvalidOid;
// ... 从 nextOid 获取并检查唯一性
return result;
}