PostgreSQL笔记20: 实例、集群、模板数据库与表空间 —— 逻辑体系架构深度解析

纲要

  • Database Cluster(数据库集群)与 Instance(实例)的概念辨析
    • Cluster 的物理存储视角
    • Instance 的运行时视角
  • DatabaseSchemaObject 的逻辑层级结构
    • 跨数据库访问的限制:dblinkpostgres_fdw
  • Template Database(模板数据库):template0template1
    • CREATE DATABASE 的复制机制
    • template0 的"原始"用途与编码/区域设置
    • datistemplatedatallowconn 标志
  • Tablespace(表空间)
    • CREATE TABLESPACE 与软链接机制
    • pg_defaultpg_global 默认表空间
    • 冷热分离的存储策略
  • OID(Object Identifier,对象标识符)
    • pg_class 系统表与 regclass 类型
    • oid2name 工具

Database Cluster 与 Instance

在 PostgreSQL 的体系架构中,Database Cluster (数据库集群,SQL 标准中称为 Catalog Cluster)与 Instance(实例)是两个最基础的概念。

Database Cluster 指的是由单个正在运行的数据库服务器实例管理的一组数据库的集合。从文件系统的角度看,一个 Database Cluster 就是一个目录(即 PGDATA 所指向的数据目录),该目录下存放了该集群中所有数据库的全部数据文件。

Instance (实例)则是指正在运行的 postgres 进程及其所管理的内存结构与后台进程。一个 postgres 实例在任何时刻都恰好管理一个 Database Cluster 的数据。在一台物理主机上,可以运行多个 PostgreSQL 实例,只要它们使用不同的数据目录和不同的通信端口即可。

在 PostgreSQL 的官方术语中,"实例"与"数据库集群"在非正式语境下有时会被混用,但严格区分如下:

  • Instance(实例) :运行时的概念,指 postgres 主进程、子进程以及共享内存等运行时资源。
  • Cluster(集群) :存储与数据的概念,指 PGDATA 目录下的全部数据文件与配置文件。

一个 Database Cluster 在初始化(通过 initdb)后,会默认包含三个数据库:postgrestemplate1template0postgres 是供工具程序和用户连接的默认数据库。

bash 复制代码
# 初始化一个数据库集群
initdb -D /usr/local/pgsql/data
bash 复制代码
# 通过 pg_ctl 启动实例
pg_ctl -D /usr/local/pgsql/data start

Database、Schema 与 Object 的逻辑层级

在一个 Database Cluster 内部,PostgreSQL 的逻辑组织结构为:Cluster → Database → Schema → Object

  • Database(数据库) :相互隔离的逻辑容器。PostgreSQL 中的各个数据库之间是严格隔离的,默认情况下不支持跨数据库的直接访问。
  • Schema(模式) :数据库内部的命名空间。默认情况下,用户创建的对象(如表、索引、视图、函数、序列、触发器、存储过程等)都位于 public 模式下。
  • Object(对象) :具体的数据库实体,包括 TABLEINDEXVIEWFUNCTIONSEQUENCETRIGGERPROCEDURE 等。

PostgreSQL 的跨数据库访问(Cross-Database Reference)默认不被支持。以下示例展示了尝试跨库查询时的错误:

sql 复制代码
-- 创建两个数据库
CREATE DATABASE mydb1;
CREATE DATABASE mydb2;

\c mydb1
CREATE TABLE t1 (id int);

-- 尝试跨库查询(会报错)
\c mydb2
SELECT * FROM mydb1.public.t1;
-- ERROR:  cross-database references are not implemented

要实现跨数据库访问,官方提供了两种标准方案:

  1. dblink 模块 :基于函数的较老方案,通过 dblink_connect() 建立连接后执行远程查询。
  2. postgres_fdw 模块:基于 SQL/MED(Foreign Data Wrapper)标准的较新方案,语法更透明,性能更优。
sql 复制代码
-- 使用 postgres_fdw 访问同一集群内的另一个数据库
CREATE EXTENSION postgres_fdw;

CREATE SERVER mydb1_server FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (dbname 'mydb1', host 'localhost', port '5432');

CREATE USER MAPPING FOR CURRENT_USER
SERVER mydb1_server
OPTIONS (user 'postgres');

CREATE FOREIGN TABLE remote_t1 (id int)
SERVER mydb1_server
OPTIONS (schema_name 'public', table_name 't1');

-- 现在可以像访问本地表一样查询远程表
SELECT * FROM remote_t1;

#mermaid-svg-eWo4lg1FH2dS8w4f{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eWo4lg1FH2dS8w4f .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eWo4lg1FH2dS8w4f .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eWo4lg1FH2dS8w4f .error-icon{fill:#552222;}#mermaid-svg-eWo4lg1FH2dS8w4f .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eWo4lg1FH2dS8w4f .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eWo4lg1FH2dS8w4f .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eWo4lg1FH2dS8w4f .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eWo4lg1FH2dS8w4f .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eWo4lg1FH2dS8w4f .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eWo4lg1FH2dS8w4f .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eWo4lg1FH2dS8w4f .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eWo4lg1FH2dS8w4f .marker.cross{stroke:#333333;}#mermaid-svg-eWo4lg1FH2dS8w4f svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eWo4lg1FH2dS8w4f p{margin:0;}#mermaid-svg-eWo4lg1FH2dS8w4f .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eWo4lg1FH2dS8w4f .cluster-label text{fill:#333;}#mermaid-svg-eWo4lg1FH2dS8w4f .cluster-label span{color:#333;}#mermaid-svg-eWo4lg1FH2dS8w4f .cluster-label span p{background-color:transparent;}#mermaid-svg-eWo4lg1FH2dS8w4f .label text,#mermaid-svg-eWo4lg1FH2dS8w4f span{fill:#333;color:#333;}#mermaid-svg-eWo4lg1FH2dS8w4f .node rect,#mermaid-svg-eWo4lg1FH2dS8w4f .node circle,#mermaid-svg-eWo4lg1FH2dS8w4f .node ellipse,#mermaid-svg-eWo4lg1FH2dS8w4f .node polygon,#mermaid-svg-eWo4lg1FH2dS8w4f .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eWo4lg1FH2dS8w4f .rough-node .label text,#mermaid-svg-eWo4lg1FH2dS8w4f .node .label text,#mermaid-svg-eWo4lg1FH2dS8w4f .image-shape .label,#mermaid-svg-eWo4lg1FH2dS8w4f .icon-shape .label{text-anchor:middle;}#mermaid-svg-eWo4lg1FH2dS8w4f .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eWo4lg1FH2dS8w4f .rough-node .label,#mermaid-svg-eWo4lg1FH2dS8w4f .node .label,#mermaid-svg-eWo4lg1FH2dS8w4f .image-shape .label,#mermaid-svg-eWo4lg1FH2dS8w4f .icon-shape .label{text-align:center;}#mermaid-svg-eWo4lg1FH2dS8w4f .node.clickable{cursor:pointer;}#mermaid-svg-eWo4lg1FH2dS8w4f .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eWo4lg1FH2dS8w4f .arrowheadPath{fill:#333333;}#mermaid-svg-eWo4lg1FH2dS8w4f .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eWo4lg1FH2dS8w4f .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eWo4lg1FH2dS8w4f .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eWo4lg1FH2dS8w4f .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eWo4lg1FH2dS8w4f .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eWo4lg1FH2dS8w4f .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eWo4lg1FH2dS8w4f .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eWo4lg1FH2dS8w4f .cluster text{fill:#333;}#mermaid-svg-eWo4lg1FH2dS8w4f .cluster span{color:#333;}#mermaid-svg-eWo4lg1FH2dS8w4f div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eWo4lg1FH2dS8w4f .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eWo4lg1FH2dS8w4f rect.text{fill:none;stroke-width:0;}#mermaid-svg-eWo4lg1FH2dS8w4f .icon-shape,#mermaid-svg-eWo4lg1FH2dS8w4f .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eWo4lg1FH2dS8w4f .icon-shape p,#mermaid-svg-eWo4lg1FH2dS8w4f .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eWo4lg1FH2dS8w4f .icon-shape .label rect,#mermaid-svg-eWo4lg1FH2dS8w4f .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eWo4lg1FH2dS8w4f .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eWo4lg1FH2dS8w4f .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eWo4lg1FH2dS8w4f :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Database Cluster
Database A
Database B
Schema: public
Schema: other
Table
Index
View
Function
Sequence
Trigger
Table
Schema: public
Table

Template Databases(模板数据库)

CREATE DATABASE 命令在 PostgreSQL 中实际上是通过复制一个现有数据库 来工作的。默认情况下,它复制名为 template1 的标准系统数据库。

template1

template1 是创建新数据库的默认模板。如果在 template1 中添加了对象(如扩展、函数、表等),这些对象会被自动复制到所有后续创建的用户数据库中。这使得 DBA 可以方便地对所有新数据库进行"站点本地定制"。

template0

template0 是第二个标准系统数据库。它包含的是 PostgreSQL 版本预定义的标准对象 ,不包含任何用户添加的内容。template0 在数据库集群初始化后不应被修改

使用 template0 作为模板创建数据库主要有两个场景:

  1. 恢复 pg_dump 转储 :确保恢复到一个"原始"状态的数据库中,避免与 template1 中的站点本地对象产生冲突。
  2. 指定不同的编码或区域设置(Locale) :复制 template1 时必须使用与 template1 相同的编码和区域设置,而复制 template0 时可以指定新的编码和区域设置。
sql 复制代码
-- 使用 template0 创建数据库,并指定不同的编码和排序规则
CREATE DATABASE mydb3
TEMPLATE template0
ENCODING 'UTF8'
LC_COLLATE 'C'
LC_CTYPE 'C';
sql 复制代码
-- 查看数据库的模板属性
SELECT datname, datistemplate, datallowconn
FROM pg_database;

pg_database 系统表中的两个标志列控制着模板数据库的行为:

  • datistemplate :若为 true,任何具有 CREATEDB 权限的用户都可以克隆该数据库;若为 false,则只有超级用户和数据库所有者可以克隆。
  • datallowconn :若为 false,则不允许对该数据库建立新的连接。template0 通常被标记为 datallowconn = false 以防止被意外修改。
sql 复制代码
-- 将一个现有数据库标记为模板数据库
UPDATE pg_database SET datistemplate = true WHERE datname = 'my_template_db';

快速克隆数据库

利用模板数据库的特性,可以快速"克隆"(fork)一个现有的数据库:

sql 复制代码
-- 基于现有的 mydb 快速创建一个新数据库 mydb_clone
CREATE DATABASE mydb_clone TEMPLATE mydb;

#mermaid-svg-SCsQ8JQBXXLQNKnR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SCsQ8JQBXXLQNKnR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SCsQ8JQBXXLQNKnR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SCsQ8JQBXXLQNKnR .error-icon{fill:#552222;}#mermaid-svg-SCsQ8JQBXXLQNKnR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SCsQ8JQBXXLQNKnR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SCsQ8JQBXXLQNKnR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SCsQ8JQBXXLQNKnR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SCsQ8JQBXXLQNKnR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SCsQ8JQBXXLQNKnR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SCsQ8JQBXXLQNKnR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SCsQ8JQBXXLQNKnR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SCsQ8JQBXXLQNKnR .marker.cross{stroke:#333333;}#mermaid-svg-SCsQ8JQBXXLQNKnR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SCsQ8JQBXXLQNKnR p{margin:0;}#mermaid-svg-SCsQ8JQBXXLQNKnR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SCsQ8JQBXXLQNKnR .cluster-label text{fill:#333;}#mermaid-svg-SCsQ8JQBXXLQNKnR .cluster-label span{color:#333;}#mermaid-svg-SCsQ8JQBXXLQNKnR .cluster-label span p{background-color:transparent;}#mermaid-svg-SCsQ8JQBXXLQNKnR .label text,#mermaid-svg-SCsQ8JQBXXLQNKnR span{fill:#333;color:#333;}#mermaid-svg-SCsQ8JQBXXLQNKnR .node rect,#mermaid-svg-SCsQ8JQBXXLQNKnR .node circle,#mermaid-svg-SCsQ8JQBXXLQNKnR .node ellipse,#mermaid-svg-SCsQ8JQBXXLQNKnR .node polygon,#mermaid-svg-SCsQ8JQBXXLQNKnR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SCsQ8JQBXXLQNKnR .rough-node .label text,#mermaid-svg-SCsQ8JQBXXLQNKnR .node .label text,#mermaid-svg-SCsQ8JQBXXLQNKnR .image-shape .label,#mermaid-svg-SCsQ8JQBXXLQNKnR .icon-shape .label{text-anchor:middle;}#mermaid-svg-SCsQ8JQBXXLQNKnR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SCsQ8JQBXXLQNKnR .rough-node .label,#mermaid-svg-SCsQ8JQBXXLQNKnR .node .label,#mermaid-svg-SCsQ8JQBXXLQNKnR .image-shape .label,#mermaid-svg-SCsQ8JQBXXLQNKnR .icon-shape .label{text-align:center;}#mermaid-svg-SCsQ8JQBXXLQNKnR .node.clickable{cursor:pointer;}#mermaid-svg-SCsQ8JQBXXLQNKnR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SCsQ8JQBXXLQNKnR .arrowheadPath{fill:#333333;}#mermaid-svg-SCsQ8JQBXXLQNKnR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SCsQ8JQBXXLQNKnR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SCsQ8JQBXXLQNKnR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SCsQ8JQBXXLQNKnR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SCsQ8JQBXXLQNKnR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SCsQ8JQBXXLQNKnR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SCsQ8JQBXXLQNKnR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SCsQ8JQBXXLQNKnR .cluster text{fill:#333;}#mermaid-svg-SCsQ8JQBXXLQNKnR .cluster span{color:#333;}#mermaid-svg-SCsQ8JQBXXLQNKnR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SCsQ8JQBXXLQNKnR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SCsQ8JQBXXLQNKnR rect.text{fill:none;stroke-width:0;}#mermaid-svg-SCsQ8JQBXXLQNKnR .icon-shape,#mermaid-svg-SCsQ8JQBXXLQNKnR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SCsQ8JQBXXLQNKnR .icon-shape p,#mermaid-svg-SCsQ8JQBXXLQNKnR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SCsQ8JQBXXLQNKnR .icon-shape .label rect,#mermaid-svg-SCsQ8JQBXXLQNKnR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SCsQ8JQBXXLQNKnR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SCsQ8JQBXXLQNKnR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SCsQ8JQBXXLQNKnR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 目标数据库
模板层
源数据库
CREATE DATABASE ... TEMPLATE mydb
默认
TEMPLATE template0
mydb
template1
template0
新数据库
新数据库

ENCODING=UTF8

LC_COLLATE=C

Tablespace(表空间)

Tablespace(表空间)允许数据库管理员在文件系统中定义存储数据库对象文件的位置。创建表空间后,可以在创建数据库对象时按名称引用。

创建表空间

sql 复制代码
-- 首先在操作系统中创建空目录
-- mkdir -p /ssd1/postgresql/data
-- chown postgres:postgres /ssd1/postgresql/data

CREATE TABLESPACE fastspace
LOCATION '/ssd1/postgresql/data';

表空间的 LOCATION 必须是一个已存在的、空目录,并且必须由 PostgreSQL 操作系统用户拥有。

默认表空间

数据库集群初始化时会自动创建两个表空间:

表空间 用途 映射路径
pg_default 存储用户数据(非共享对象) $PGDATA/base/
pg_global 存储共享系统目录 $PGDATA/global/

pg_defaulttemplate1template0 的默认表空间,因此也是绝大多数新数据库的默认表空间。

表空间的使用

sql 复制代码
-- 在指定表空间中创建表
CREATE TABLE foo (i int) TABLESPACE fastspace;

-- 在指定表空间中创建索引
CREATE INDEX idx_foo_i ON foo(i) TABLESPACE fastspace;

-- 创建数据库时指定默认表空间
CREATE DATABASE mydb TABLESPACE fastspace;
sql 复制代码
-- 设置会话级别的默认表空间
SET default_tablespace = fastspace;
CREATE TABLE bar (j int);  -- 自动使用 fastspace

冷热分离策略

表空间的核心应用场景之一是冷热数据分离

  • 热数据 (频繁访问的表和索引)放置在 SSD 上的表空间(如 fastspace)。
  • 冷数据 (历史归档表、不常访问的索引)放置在大容量 HDD 上的表空间(如 slowspace)。

表空间的物理存储

PostgreSQL 中的表空间在文件系统层面通过软链接(symlink) 实现。表空间目录下会有一个 PG_<version>_<timestamp> 子目录,其中存储了该表空间中各数据库的 OID 目录。

bash 复制代码
# 查看表空间的软链接
ls -la $PGDATA/pg_tblspc/
# 示例输出:
# lrwxrwxrwx 1 postgres postgres 25 Jan 15 10:00 16384 -> /ssd1/postgresql/data

表空间的限制

  • 表空间是数据库集群的不可分割的一部分,不能单独备份或迁移到另一个集群。
  • 如果表空间丢失或损坏,整个数据库集群可能无法启动。
  • 表空间的存储容量受限于底层文件系统的容量。

#mermaid-svg-vReobg0Iv7KkJMJO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vReobg0Iv7KkJMJO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vReobg0Iv7KkJMJO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vReobg0Iv7KkJMJO .error-icon{fill:#552222;}#mermaid-svg-vReobg0Iv7KkJMJO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vReobg0Iv7KkJMJO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vReobg0Iv7KkJMJO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vReobg0Iv7KkJMJO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vReobg0Iv7KkJMJO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vReobg0Iv7KkJMJO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vReobg0Iv7KkJMJO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vReobg0Iv7KkJMJO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vReobg0Iv7KkJMJO .marker.cross{stroke:#333333;}#mermaid-svg-vReobg0Iv7KkJMJO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vReobg0Iv7KkJMJO p{margin:0;}#mermaid-svg-vReobg0Iv7KkJMJO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vReobg0Iv7KkJMJO .cluster-label text{fill:#333;}#mermaid-svg-vReobg0Iv7KkJMJO .cluster-label span{color:#333;}#mermaid-svg-vReobg0Iv7KkJMJO .cluster-label span p{background-color:transparent;}#mermaid-svg-vReobg0Iv7KkJMJO .label text,#mermaid-svg-vReobg0Iv7KkJMJO span{fill:#333;color:#333;}#mermaid-svg-vReobg0Iv7KkJMJO .node rect,#mermaid-svg-vReobg0Iv7KkJMJO .node circle,#mermaid-svg-vReobg0Iv7KkJMJO .node ellipse,#mermaid-svg-vReobg0Iv7KkJMJO .node polygon,#mermaid-svg-vReobg0Iv7KkJMJO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vReobg0Iv7KkJMJO .rough-node .label text,#mermaid-svg-vReobg0Iv7KkJMJO .node .label text,#mermaid-svg-vReobg0Iv7KkJMJO .image-shape .label,#mermaid-svg-vReobg0Iv7KkJMJO .icon-shape .label{text-anchor:middle;}#mermaid-svg-vReobg0Iv7KkJMJO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vReobg0Iv7KkJMJO .rough-node .label,#mermaid-svg-vReobg0Iv7KkJMJO .node .label,#mermaid-svg-vReobg0Iv7KkJMJO .image-shape .label,#mermaid-svg-vReobg0Iv7KkJMJO .icon-shape .label{text-align:center;}#mermaid-svg-vReobg0Iv7KkJMJO .node.clickable{cursor:pointer;}#mermaid-svg-vReobg0Iv7KkJMJO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vReobg0Iv7KkJMJO .arrowheadPath{fill:#333333;}#mermaid-svg-vReobg0Iv7KkJMJO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vReobg0Iv7KkJMJO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vReobg0Iv7KkJMJO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vReobg0Iv7KkJMJO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vReobg0Iv7KkJMJO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vReobg0Iv7KkJMJO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vReobg0Iv7KkJMJO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vReobg0Iv7KkJMJO .cluster text{fill:#333;}#mermaid-svg-vReobg0Iv7KkJMJO .cluster span{color:#333;}#mermaid-svg-vReobg0Iv7KkJMJO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vReobg0Iv7KkJMJO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vReobg0Iv7KkJMJO rect.text{fill:none;stroke-width:0;}#mermaid-svg-vReobg0Iv7KkJMJO .icon-shape,#mermaid-svg-vReobg0Iv7KkJMJO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vReobg0Iv7KkJMJO .icon-shape p,#mermaid-svg-vReobg0Iv7KkJMJO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vReobg0Iv7KkJMJO .icon-shape .label rect,#mermaid-svg-vReobg0Iv7KkJMJO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vReobg0Iv7KkJMJO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vReobg0Iv7KkJMJO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vReobg0Iv7KkJMJO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 自定义表空间
$PGDATA (数据目录)
pg_global
pg_default
base/
global/
pg_tblspc/
软链接 (OID)
PG__/
数据库 OID 目录/
数据文件
共享系统目录
用户数据

OID(Object Identifier,对象标识符)

OID(Object Identifier,对象标识符)是 PostgreSQL 内部用于标识各种系统表主键的 32 位整数。

在文件系统层面,PostgreSQL 使用 OID 来组织数据文件。$PGDATA/base/ 目录下的数字目录名就是数据库的 OID,而其中的数字文件名则是表或索引的 relfilenode(通常与 OID 一致或相关)。

sql 复制代码
-- 查询数据库的 OID
SELECT oid, datname FROM pg_database;

-- 查询表的 OID 和 relfilenode
SELECT oid, relname, relfilenode
FROM pg_class
WHERE relname = 'foo';

PostgreSQL 提供了 regclass 别名类型,可以将表名转换为 OID:

sql 复制代码
-- 使用 regclass 将表名转换为 OID
SELECT 'foo'::regclass::oid;

oid2name 工具

PostgreSQL 提供了 oid2name 命令行工具,用于将 OID 映射为对应的对象名称:

bash 复制代码
# 查看指定 OID 对应的对象名称
oid2name -d mydb -o 16384

# 列出数据库中所有表及其 OID
oid2name -d mydb -s

API 速览(API Overview)

数据库管理 API

API 所属模块 方法签名 说明
CREATE DATABASE PostgreSQL DDL CREATE DATABASE name [WITH] [TEMPLATE template] [ENCODING encoding] [LC_COLLATE collate] [LC_CTYPE ctype] [TABLESPACE tablespace] 创建新数据库,可通过 TEMPLATE 指定模板
DROP DATABASE PostgreSQL DDL DROP DATABASE name 删除数据库
ALTER DATABASE PostgreSQL DDL ALTER DATABASE name SET parameter = value 修改数据库配置

表空间管理 API

API 所属模块 方法签名 说明
CREATE TABLESPACE PostgreSQL DDL CREATE TABLESPACE name LOCATION 'directory' 创建表空间
DROP TABLESPACE PostgreSQL DDL DROP TABLESPACE name 删除空表空间
SET default_tablespace PostgreSQL GUC SET default_tablespace = 'name' 设置会话级默认表空间

系统目录查询 API

API 所属模块 方法签名 说明
pg_database 系统表 SELECT * FROM pg_database 查询所有数据库及其属性(datistemplatedatallowconn
pg_class 系统表 SELECT * FROM pg_class 查询所有表和索引的元数据(oidrelnamerelfilenode
::regclass PostgreSQL 类型转换 'table_name'::regclass 将表名转换为 OID
oid2name 命令行工具 oid2name -d dbname -o oid 将 OID 映射为对象名称

跨库访问 API

API 所属模块 方法签名 说明
dblink_connect dblink dblink_connect(text connstr) 建立跨库连接
dblink dblink dblink(text connname, text sql) 执行远程 SQL 查询
CREATE FOREIGN DATA WRAPPER postgres_fdw CREATE FOREIGN DATA WRAPPER name 创建外部数据包装器
CREATE SERVER postgres_fdw CREATE SERVER name FOREIGN DATA WRAPPER fdw OPTIONS (...) 创建外部服务器
CREATE FOREIGN TABLE postgres_fdw CREATE FOREIGN TABLE name (...) SERVER server OPTIONS (...) 创建外部表映射

Demo 完整示例(Complete Demo)

以下是一个基于 Node.jsnode-postgrespg 库)的完整演示,涵盖数据库连接、模板数据库创建、表空间操作和系统目录查询。

bash 复制代码
# 项目初始化
mkdir pg-demo && cd pg-demo
npm init -y
npm install pg
ts 复制代码
// index.ts
import { Client, Pool } from 'pg';

// PostgreSQL 连接配置
const config = {
  host: 'localhost',
  port: 5432,
  user: 'postgres',
  password: 'postgres',
  database: 'postgres',
};

// 创建连接池
const pool = new Pool(config);

/**
 * 执行 SQL 查询的辅助函数
 */
async function executeQuery(sql: string, params: any[] = []): Promise<any> {
  const client = await pool.connect();
  try {
    const result = await client.query(sql, params);
    return result.rows;
  } finally {
    client.release();
  }
}

/**
 * 核心 Demo:展示 PostgreSQL 逻辑体系架构的核心操作
 */
async function main() {
  console.log('=== PostgreSQL 逻辑体系架构 Demo ===\n');

  // 1. 查看数据库集群中的数据库列表
  console.log('1. 当前数据库集群中的所有数据库:');
  const dbs = await executeQuery(
    `SELECT oid, datname, datistemplate, datallowconn 
     FROM pg_database 
     ORDER BY datname`
  );
  console.table(dbs);

  // 2. 创建模板数据库
  console.log('\n2. 创建一个自定义模板数据库 (my_template):');
  await executeQuery(`DROP DATABASE IF EXISTS my_template`);
  await executeQuery(`CREATE DATABASE my_template`);
  await executeQuery(`\c my_template`);
  await executeQuery(`CREATE TABLE template_table (id int, name text)`);
  await executeQuery(`INSERT INTO template_table VALUES (1, 'template data')`);
  // 标记为模板数据库
  await executeQuery(
    `UPDATE pg_database SET datistemplate = true WHERE datname = 'my_template'`
  );
  console.log('   ✓ 模板数据库 my_template 创建完成,并标记为 datistemplate = true');

  // 3. 基于模板数据库创建新数据库
  console.log('\n3. 基于 my_template 克隆新数据库 (cloned_db):');
  await executeQuery(`DROP DATABASE IF EXISTS cloned_db`);
  await executeQuery(`CREATE DATABASE cloned_db TEMPLATE my_template`);
  console.log('   ✓ 克隆数据库 cloned_db 创建完成');

  // 4. 验证克隆结果
  console.log('\n4. 验证克隆结果 (查询 cloned_db 中的数据):');
  const rows = await executeQuery(
    `SELECT * FROM cloned_db.public.template_table`
  );
  console.table(rows);

  // 5. 使用 template0 创建指定编码的数据库
  console.log('\n5. 使用 template0 创建指定 LC_COLLATE=C 的数据库 (mydb_c):');
  await executeQuery(`DROP DATABASE IF EXISTS mydb_c`);
  await executeQuery(`
    CREATE DATABASE mydb_c 
    TEMPLATE template0 
    ENCODING 'UTF8' 
    LC_COLLATE 'C' 
    LC_CTYPE 'C'
  `);
  console.log('   ✓ 数据库 mydb_c 创建完成 (LC_COLLATE = C)');

  // 6. 验证排序规则
  console.log('\n6. 验证数据库的排序规则:');
  const collateInfo = await executeQuery(
    `SELECT datname, datcollate, datctype FROM pg_database WHERE datname = 'mydb_c'`
  );
  console.table(collateInfo);

  // 7. 表空间操作
  console.log('\n7. 表空间操作:');
  // 查看现有表空间
  const tablespaces = await executeQuery(
    `SELECT spcname, pg_tablespace_location(oid) as location 
     FROM pg_tablespace`
  );
  console.log('   当前表空间列表:');
  console.table(tablespaces);

  // 8. OID 查询
  console.log('\n8. OID 查询示例:');
  // 切换到 cloned_db 创建测试表
  await executeQuery(`\c cloned_db`);
  await executeQuery(`CREATE TABLE IF NOT EXISTS test_oid (id int)`);
  const oidInfo = await executeQuery(
    `SELECT oid, relname, relfilenode 
     FROM pg_class 
     WHERE relname = 'test_oid'`
  );
  console.table(oidInfo);

  // 使用 regclass 转换
  const regclassResult = await executeQuery(
    `SELECT 'test_oid'::regclass::oid as oid_from_regclass`
  );
  console.log('   通过 regclass 获取 OID:', regclassResult[0]?.oid_from_regclass);

  console.log('\n=== Demo 执行完成 ===');
}

// 执行主函数
main()
  .then(() => process.exit(0))
  .catch((err) => {
    console.error('Demo 执行失败:', err);
    process.exit(1);
  });

运行说明

bash 复制代码
# 确保 PostgreSQL 已启动
# 方式一:使用 TypeScript(需安装 ts-node)
npx ts-node index.ts

# 方式二:使用 Node.js(将 .ts 改为 .js 或使用编译后的文件)
node index.js

关键技术点总结

  1. 模板数据库克隆CREATE DATABASE ... TEMPLATE 实现了数据库级别的快速复制。
  2. datistemplate 标志:控制数据库是否可作为模板被克隆。
  3. template0 与编码/区域设置 :使用 template0 可以突破 template1 的编码限制。
  4. pg_database 系统表:查询数据库的 OID、模板属性和连接属性。
  5. pg_classregclass :获取表和索引的 OID 及 relfilenode
  6. 表空间查询pg_tablespace 系统表与 pg_tablespace_location() 函数。

多语言示例

本节提供与 Node.js Demo 功能等价的 Go、Python 和 Java 实现,涵盖数据库连接、模板数据库克隆、表空间查询和 OID 查询等核心操作。

Go 示例

使用 pgx 驱动(推荐)或 lib/pq。以下基于 pgx/v5

bash 复制代码
# 项目初始化
mkdir pg-demo-go && cd pg-demo-go
go mod init pg-demo
go get github.com/jackc/pgx/v5
go 复制代码
// main.go
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/jackc/pgx/v5"
)

func main() {
	ctx := context.Background()

	// 连接到 postgres 数据库
	connStr := "postgres://postgres:postgres@localhost:5432/postgres"
	conn, err := pgx.Connect(ctx, connStr)
	if err != nil {
		log.Fatalf("连接失败: %v", err)
	}
	defer conn.Close(ctx)

	fmt.Println("=== PostgreSQL 逻辑体系架构 Demo (Go) ===\n")

	// 1. 查看所有数据库
	fmt.Println("1. 当前数据库集群中的所有数据库:")
	rows, err := conn.Query(ctx, `SELECT oid, datname, datistemplate, datallowconn FROM pg_database ORDER BY datname`)
	if err != nil {
		log.Fatalf("查询失败: %v", err)
	}
	defer rows.Close()
	for rows.Next() {
		var oid uint32
		var datname string
		var datistemplate, datallowconn bool
		if err := rows.Scan(&oid, &datname, &datistemplate, &datallowconn); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("  OID: %d, 名称: %s, 可作模板: %v, 允许连接: %v\n", oid, datname, datistemplate, datallowconn)
	}
	if err = rows.Err(); err != nil {
		log.Fatal(err)
	}

	// 2. 创建自定义模板数据库
	fmt.Println("\n2. 创建自定义模板数据库 (my_template):")
	_, err = conn.Exec(ctx, `DROP DATABASE IF EXISTS my_template`)
	if err != nil {
		log.Fatal(err)
	}
	_, err = conn.Exec(ctx, `CREATE DATABASE my_template`)
	if err != nil {
		log.Fatal(err)
	}
	// 切换到 my_template 创建表
	_, err = conn.Exec(ctx, `\c my_template`) // 注意:pgx 不支持 \c,需要新建连接
	// 改为新建连接
	connTemplate, err := pgx.Connect(ctx, "postgres://postgres:postgres@localhost:5432/my_template")
	if err != nil {
		log.Fatal(err)
	}
	defer connTemplate.Close(ctx)
	_, err = connTemplate.Exec(ctx, `CREATE TABLE template_table (id int, name text)`)
	if err != nil {
		log.Fatal(err)
	}
	_, err = connTemplate.Exec(ctx, `INSERT INTO template_table VALUES (1, 'template data')`)
	if err != nil {
		log.Fatal(err)
	}
	// 标记为模板数据库
	_, err = conn.Exec(ctx, `UPDATE pg_database SET datistemplate = true WHERE datname = 'my_template'`)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("   ✓ 模板数据库 my_template 创建完成")

	// 3. 基于模板克隆
	fmt.Println("\n3. 基于 my_template 克隆新数据库 (cloned_db):")
	_, err = conn.Exec(ctx, `DROP DATABASE IF EXISTS cloned_db`)
	if err != nil {
		log.Fatal(err)
	}
	_, err = conn.Exec(ctx, `CREATE DATABASE cloned_db TEMPLATE my_template`)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("   ✓ 克隆数据库 cloned_db 创建完成")

	// 4. 验证克隆数据
	fmt.Println("\n4. 验证克隆结果:")
	connClone, err := pgx.Connect(ctx, "postgres://postgres:postgres@localhost:5432/cloned_db")
	if err != nil {
		log.Fatal(err)
	}
	defer connClone.Close(ctx)
	var id int
	var name string
	err = connClone.QueryRow(ctx, `SELECT id, name FROM template_table`).Scan(&id, &name)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("   cloned_db 中数据: id=%d, name=%s\n", id, name)

	// 5. 使用 template0 创建指定编码数据库
	fmt.Println("\n5. 使用 template0 创建 LC_COLLATE=C 的数据库 (mydb_c):")
	_, err = conn.Exec(ctx, `DROP DATABASE IF EXISTS mydb_c`)
	if err != nil {
		log.Fatal(err)
	}
	_, err = conn.Exec(ctx, `
		CREATE DATABASE mydb_c
		TEMPLATE template0
		ENCODING 'UTF8'
		LC_COLLATE 'C'
		LC_CTYPE 'C'
	`)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("   ✓ 数据库 mydb_c 创建完成")

	// 6. 查询排序规则
	fmt.Println("\n6. 验证数据库的排序规则:")
	var datcollate, datctype string
	err = conn.QueryRow(ctx, `SELECT datcollate, datctype FROM pg_database WHERE datname = 'mydb_c'`).Scan(&datcollate, &datctype)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("   datcollate: %s, datctype: %s\n", datcollate, datctype)

	// 7. 表空间列表
	fmt.Println("\n7. 当前表空间列表:")
	rowsTS, err := conn.Query(ctx, `SELECT spcname, pg_tablespace_location(oid) FROM pg_tablespace`)
	if err != nil {
		log.Fatal(err)
	}
	defer rowsTS.Close()
	for rowsTS.Next() {
		var spcname string
		var location *string
		if err := rowsTS.Scan(&spcname, &location); err != nil {
			log.Fatal(err)
		}
		loc := "NULL"
		if location != nil {
			loc = *location
		}
		fmt.Printf("   %s -> %s\n", spcname, loc)
	}

	// 8. OID 查询
	fmt.Println("\n8. OID 查询示例:")
	// 在 cloned_db 中建表
	_, err = connClone.Exec(ctx, `CREATE TABLE IF NOT EXISTS test_oid (id int)`)
	if err != nil {
		log.Fatal(err)
	}
	var oidVal uint32
	var relname, relfilenode string
	err = connClone.QueryRow(ctx, `SELECT oid, relname, relfilenode::text FROM pg_class WHERE relname = 'test_oid'`).Scan(&oidVal, &relname, &relfilenode)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("   test_oid: OID=%d, relfilenode=%s\n", oidVal, relfilenode)

	// 使用 regclass
	var oidFromRegclass uint32
	err = connClone.QueryRow(ctx, `SELECT 'test_oid'::regclass::oid`).Scan(&oidFromRegclass)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("   通过 regclass 获取 OID: %d\n", oidFromRegclass)

	fmt.Println("\n=== Demo 执行完成 ===")
}

运行说明:

bash 复制代码
go run main.go

Python 示例

使用 psycopg2(推荐)或 asyncpg。以下基于 psycopg2

bash 复制代码
# 安装依赖
pip install psycopg2-binary
python 复制代码
# main.py
import psycopg2
from psycopg2 import sql

def main():
    conn = psycopg2.connect(
        host="localhost",
        port=5432,
        user="postgres",
        password="postgres",
        dbname="postgres"
    )
    conn.autocommit = True
    cur = conn.cursor()

    print("=== PostgreSQL 逻辑体系架构 Demo (Python) ===\n")

    # 1. 查看所有数据库
    print("1. 当前数据库集群中的所有数据库:")
    cur.execute("SELECT oid, datname, datistemplate, datallowconn FROM pg_database ORDER BY datname")
    for oid, datname, datistemplate, datallowconn in cur.fetchall():
        print(f"  OID: {oid}, 名称: {datname}, 可作模板: {datistemplate}, 允许连接: {datallowconn}")

    # 2. 创建模板数据库
    print("\n2. 创建自定义模板数据库 (my_template):")
    cur.execute("DROP DATABASE IF EXISTS my_template")
    cur.execute("CREATE DATABASE my_template")
    # 切换到 my_template
    conn_template = psycopg2.connect(
        host="localhost", port=5432, user="postgres", password="postgres", dbname="my_template"
    )
    conn_template.autocommit = True
    cur_t = conn_template.cursor()
    cur_t.execute("CREATE TABLE template_table (id int, name text)")
    cur_t.execute("INSERT INTO template_table VALUES (1, 'template data')")
    cur_t.close()
    conn_template.close()
    # 标记为模板
    cur.execute("UPDATE pg_database SET datistemplate = true WHERE datname = 'my_template'")
    print("   ✓ 模板数据库 my_template 创建完成")

    # 3. 克隆
    print("\n3. 基于 my_template 克隆新数据库 (cloned_db):")
    cur.execute("DROP DATABASE IF EXISTS cloned_db")
    cur.execute("CREATE DATABASE cloned_db TEMPLATE my_template")
    print("   ✓ 克隆数据库 cloned_db 创建完成")

    # 4. 验证
    print("\n4. 验证克隆结果:")
    conn_clone = psycopg2.connect(
        host="localhost", port=5432, user="postgres", password="postgres", dbname="cloned_db"
    )
    conn_clone.autocommit = True
    cur_c = conn_clone.cursor()
    cur_c.execute("SELECT id, name FROM template_table")
    row = cur_c.fetchone()
    print(f"   cloned_db 中数据: id={row[0]}, name={row[1]}")
    cur_c.close()
    conn_clone.close()

    # 5. 使用 template0
    print("\n5. 使用 template0 创建 LC_COLLATE=C 的数据库 (mydb_c):")
    cur.execute("DROP DATABASE IF EXISTS mydb_c")
    cur.execute("""
        CREATE DATABASE mydb_c
        TEMPLATE template0
        ENCODING 'UTF8'
        LC_COLLATE 'C'
        LC_CTYPE 'C'
    """)
    print("   ✓ 数据库 mydb_c 创建完成")

    # 6. 排序规则
    print("\n6. 验证数据库的排序规则:")
    cur.execute("SELECT datcollate, datctype FROM pg_database WHERE datname = 'mydb_c'")
    datcollate, datctype = cur.fetchone()
    print(f"   datcollate: {datcollate}, datctype: {datctype}")

    # 7. 表空间
    print("\n7. 当前表空间列表:")
    cur.execute("SELECT spcname, pg_tablespace_location(oid) FROM pg_tablespace")
    for spcname, location in cur.fetchall():
        loc = location if location else "NULL"
        print(f"   {spcname} -> {loc}")

    # 8. OID
    print("\n8. OID 查询示例:")
    # 在 cloned_db 中操作
    conn_clone2 = psycopg2.connect(
        host="localhost", port=5432, user="postgres", password="postgres", dbname="cloned_db"
    )
    conn_clone2.autocommit = True
    cur_c2 = conn_clone2.cursor()
    cur_c2.execute("CREATE TABLE IF NOT EXISTS test_oid (id int)")
    cur_c2.execute("SELECT oid, relname, relfilenode::text FROM pg_class WHERE relname = 'test_oid'")
    oid_val, relname, relfilenode = cur_c2.fetchone()
    print(f"   test_oid: OID={oid_val}, relfilenode={relfilenode}")
    cur_c2.execute("SELECT 'test_oid'::regclass::oid")
    oid_reg = cur_c2.fetchone()[0]
    print(f"   通过 regclass 获取 OID: {oid_reg}")
    cur_c2.close()
    conn_clone2.close()

    cur.close()
    conn.close()
    print("\n=== Demo 执行完成 ===")

if __name__ == "__main__":
    main()

运行说明:

bash 复制代码
python main.py

Java 示例

使用 JDBC 驱动(org.postgresql:postgresql)。以下基于 Maven 项目。

bash 复制代码
# 创建 Maven 项目
mvn archetype:generate -DgroupId=com.example -DartifactId=pg-demo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
cd pg-demo
# 编辑 pom.xml 添加依赖
# 然后在 src/main/java/com/example/ 下创建 App.java
xml 复制代码
<!-- pom.xml 依赖片段 -->
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.3</version>
</dependency>
java 复制代码
// src/main/java/com/example/App.java
package com.example;

import java.sql.*;
import java.util.Properties;

public class App {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/postgres";
        Properties props = new Properties();
        props.setProperty("user", "postgres");
        props.setProperty("password", "postgres");

        try (Connection conn = DriverManager.getConnection(url, props)) {
            conn.setAutoCommit(true);
            System.out.println("=== PostgreSQL 逻辑体系架构 Demo (Java) ===\n");

            // 1. 查看所有数据库
            System.out.println("1. 当前数据库集群中的所有数据库:");
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery(
                         "SELECT oid, datname, datistemplate, datallowconn FROM pg_database ORDER BY datname")) {
                while (rs.next()) {
                    int oid = rs.getInt("oid");
                    String datname = rs.getString("datname");
                    boolean datistemplate = rs.getBoolean("datistemplate");
                    boolean datallowconn = rs.getBoolean("datallowconn");
                    System.out.printf("  OID: %d, 名称: %s, 可作模板: %b, 允许连接: %b%n",
                            oid, datname, datistemplate, datallowconn);
                }
            }

            // 2. 创建模板数据库
            System.out.println("\n2. 创建自定义模板数据库 (my_template):");
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("DROP DATABASE IF EXISTS my_template");
                stmt.execute("CREATE DATABASE my_template");
            }
            // 连接到 my_template
            String urlTemplate = "jdbc:postgresql://localhost:5432/my_template";
            try (Connection connTemplate = DriverManager.getConnection(urlTemplate, props);
                 Statement stmtT = connTemplate.createStatement()) {
                stmtT.execute("CREATE TABLE template_table (id int, name text)");
                stmtT.execute("INSERT INTO template_table VALUES (1, 'template data')");
            }
            // 标记为模板
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("UPDATE pg_database SET datistemplate = true WHERE datname = 'my_template'");
            }
            System.out.println("   ✓ 模板数据库 my_template 创建完成");

            // 3. 克隆
            System.out.println("\n3. 基于 my_template 克隆新数据库 (cloned_db):");
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("DROP DATABASE IF EXISTS cloned_db");
                stmt.execute("CREATE DATABASE cloned_db TEMPLATE my_template");
            }
            System.out.println("   ✓ 克隆数据库 cloned_db 创建完成");

            // 4. 验证
            System.out.println("\n4. 验证克隆结果:");
            String urlClone = "jdbc:postgresql://localhost:5432/cloned_db";
            try (Connection connClone = DriverManager.getConnection(urlClone, props);
                 Statement stmtC = connClone.createStatement();
                 ResultSet rs = stmtC.executeQuery("SELECT id, name FROM template_table")) {
                if (rs.next()) {
                    int id = rs.getInt("id");
                    String name = rs.getString("name");
                    System.out.printf("   cloned_db 中数据: id=%d, name=%s%n", id, name);
                }
            }

            // 5. 使用 template0
            System.out.println("\n5. 使用 template0 创建 LC_COLLATE=C 的数据库 (mydb_c):");
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("DROP DATABASE IF EXISTS mydb_c");
                stmt.execute("CREATE DATABASE mydb_c TEMPLATE template0 ENCODING 'UTF8' LC_COLLATE 'C' LC_CTYPE 'C'");
            }
            System.out.println("   ✓ 数据库 mydb_c 创建完成");

            // 6. 排序规则
            System.out.println("\n6. 验证数据库的排序规则:");
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT datcollate, datctype FROM pg_database WHERE datname = 'mydb_c'")) {
                if (rs.next()) {
                    String datcollate = rs.getString("datcollate");
                    String datctype = rs.getString("datctype");
                    System.out.printf("   datcollate: %s, datctype: %s%n", datcollate, datctype);
                }
            }

            // 7. 表空间
            System.out.println("\n7. 当前表空间列表:");
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT spcname, pg_tablespace_location(oid) FROM pg_tablespace")) {
                while (rs.next()) {
                    String spcname = rs.getString(1);
                    String location = rs.getString(2);
                    String loc = (location == null) ? "NULL" : location;
                    System.out.printf("   %s -> %s%n", spcname, loc);
                }
            }

            // 8. OID
            System.out.println("\n8. OID 查询示例:");
            String urlClone2 = "jdbc:postgresql://localhost:5432/cloned_db";
            try (Connection connClone2 = DriverManager.getConnection(urlClone2, props);
                 Statement stmtC2 = connClone2.createStatement()) {
                stmtC2.execute("CREATE TABLE IF NOT EXISTS test_oid (id int)");
                try (ResultSet rs = stmtC2.executeQuery(
                        "SELECT oid, relname, relfilenode::text FROM pg_class WHERE relname = 'test_oid'")) {
                    if (rs.next()) {
                        int oidVal = rs.getInt("oid");
                        String relname = rs.getString("relname");
                        String relfilenode = rs.getString("relfilenode");
                        System.out.printf("   test_oid: OID=%d, relfilenode=%s%n", oidVal, relfilenode);
                    }
                }
                try (ResultSet rs = stmtC2.executeQuery("SELECT 'test_oid'::regclass::oid")) {
                    if (rs.next()) {
                        int oidReg = rs.getInt(1);
                        System.out.printf("   通过 regclass 获取 OID: %d%n", oidReg);
                    }
                }
            }

            System.out.println("\n=== Demo 执行完成 ===");

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

运行说明:

bash 复制代码
mvn compile
mvn exec:java -Dexec.mainClass="com.example.App"

多语言示例技术点总结

语言 驱动/库 连接字符串示例 关键特性
Node.js pg postgres://user:pass@host:port/db 基于 Pool 连接池,支持异步/await
Go pgx/v5 postgres://user:pass@host:port/db 高性能,支持上下文,自动重连
Python psycopg2 DSN 或关键字参数 成熟稳定,支持 autocommit 和原生 SQL 拼接
Java JDBC (PostgreSQL 驱动) jdbc:postgresql://host:port/db 标准 JDBC API,需手动管理连接

所有示例均实现了以下核心功能:

  • 查询系统目录 pg_databasepg_class
  • 创建自定义模板数据库并标记 datistemplate = true
  • 使用 TEMPLATE 子句克隆数据库
  • 使用 template0 创建指定编码和 LC_COLLATE 的数据库
  • 查询表空间及其物理位置
  • 通过 regclass 转换获取 OID
  • 切换数据库连接(通过新建连接模拟 \c

官方文档与参考链接

官方文档

参考链接

总结

本文深入探讨了 PostgreSQL 逻辑体系架构中的核心概念:

  • Database Cluster(数据库集群) 是存储层面的概念,指 PGDATA 目录下由单个实例管理的所有数据库的集合。
  • Instance(实例) 是运行时的概念,指 postgres 进程及其管理的内存与后台进程。
  • 模板数据库(template1 / template0CREATE DATABASE 的复制源,template1 支持站点本地定制,template0 提供"原始"环境并支持自定义编码与区域设置。
  • 表空间(Tablespace) 允许 DBA 控制数据库对象的物理存储位置,实现冷热分离与性能优化。
  • OID(对象标识符) 是 PostgreSQL 内部用于标识系统表主键和文件组织的基础机制。
  • 跨数据库访问可通过 dblinkpostgres_fdw 实现,其中 postgres_fdw 提供了更符合 SQL/MED 标准的语法和更好的性能。
相关推荐
wp123_18 小时前
硬件元器件笔记|IPX8 防水 Type‑C 母座安费诺 124018802112A 与 TONEVEE TY48086‑24A 分析
c语言·开发语言·笔记
杨运交9 小时前
[060][调度模块]Redisson vs Redis 原生锁:两种分布式锁实现深度对比
数据库·redis·分布式
FungLeo9 小时前
Node 后端实战 · 列表查询到底怎么写?一个通用 DSL 封装,过滤分页排序一次搞定
数据库·node.js·serverless·dsl 封装·通用列表查询
mifengxing9 小时前
操作系统 文件系统
笔记·操作系统·计算机408
抓蛙师10 小时前
多租户 tenant_id SQL 注入漏洞分析与应急响应报告
数据库·sql
ACP广源盛1392462567310 小时前
Qwen3.8‑2.4T 开源落地@ACP#国产 MoE 私有化部署下 GSV2221 视频转换芯片机遇分析
大数据·数据库·人工智能
无聊的菜鸟11 小时前
TMS320F2806X学习笔记(零)—— C2000、CCS、新建工程
笔记·mcu·c2000·f2806x
七牛开发者11 小时前
为什么 Go 很适合 AI 辅助开发?
数据库·人工智能·python·elasticsearch·log4j
ljt272496066112 小时前
Compose笔记(八十三)--onVisibilityChanged
笔记