跳到主要内容

模式

模式(schema)是数据库内部的命名空间。它把表、视图、函数、类型等对象分组,使不同模式中可以存在同名对象:

sales.orders
archive.orders

模式适合按业务边界、权限边界或生命周期组织对象,但它不是独立数据库:同一数据库中的模式共享连接、事务和许多数据库级配置。

本文以 PostgreSQL 为主。其他数据库可能把 schema 与“数据库”“用户”绑定,迁移设计时不能只按名称类比。

限定名称​

完整对象名由模式名和对象名组成:

SELECT *
FROM sales.orders;

这称为模式限定名称(schema-qualified name)。显式限定有几个好处:

  • 读者知道对象来自哪里;
  • 不依赖会话的名称搜索路径;
  • 避免同名对象产生歧义;
  • 安全敏感代码更不容易被恶意对象劫持。

创建和删除模式​

创建模式:

CREATE SCHEMA sales;

指定所有者:

CREATE SCHEMA AUTHORIZATION sales_owner;

也可以同时指定名称和所有者:

CREATE SCHEMA sales AUTHORIZATION sales_owner;

删除空模式:

DROP SCHEMA sales;

删除模式及其中所有对象:

DROP SCHEMA sales CASCADE;

CASCADE 可能删除表、视图、函数以及其他模式中依赖它们的对象。生产环境应先检查依赖,并优先使用明确的迁移脚本逐项处理。

search_path​

未限定的对象名通过 search_path 解析:

SHOW search_path;

常见设置可能包含:

"$user", public

"$user" 表示与当前角色同名且存在的模式。解析 orders 时,PostgreSQL 会按有效搜索路径寻找第一个匹配对象。

设置当前会话的搜索路径:

SET search_path TO sales, public;

只在当前事务内设置:

SET LOCAL search_path TO sales, pg_catalog;

第一个有效模式通常也是未限定创建对象时的目标位置:

CREATE TABLE invoices (...);

因此 search_path 不只影响读取,也可能决定对象被创建在哪里。

当前模式​

查看当前默认创建模式:

SELECT current_schema();

查看有效搜索路径:

SELECT current_schemas(TRUE);

参数 TRUE 会包含隐式搜索的系统模式。

pg_catalog 与临时模式​

PostgreSQL 的系统表和内置函数位于 pg_catalog。即使未显式列出,它通常也会被搜索;若显式列入,则顺序会影响解析。

临时表位于会话专属的临时模式中,常通过 pg_temp 引用:

CREATE TEMP TABLE work_items (...);
SELECT * FROM pg_temp.work_items;

临时模式在关系名称解析中有较高优先级,但函数和操作符解析有不同规则。安全敏感代码不要依赖对这些细节的模糊记忆,应使用限定名称并固定安全路径。

模式权限​

模式主要有两类普通权限:

  • USAGE:允许解析和访问模式中的对象名称;
  • CREATE:允许在模式中创建对象。
GRANT USAGE ON SCHEMA sales TO app_runtime;
GRANT CREATE ON SCHEMA scratch TO developer_role;

拥有模式 USAGE 不等于拥有其中表的 SELECT 权限;拥有 CREATE 也不等于能读取已有对象。访问一张表通常同时需要模式和表两层权限。

GRANT USAGE ON SCHEMA sales TO app_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA sales TO app_readonly;

未来新表还需要默认权限配置。

public 模式​

新数据库通常包含名为 public 的模式。它只是普通模式加上一些初始约定,不等于 PUBLIC 角色:

  • public:一个模式名称;
  • PUBLIC:代表所有角色的伪角色。

不同 PostgreSQL 版本、安装模板和云服务可能对 public 的默认 CREATE 权限采取不同设置。应查询实际权限,而不是假设任何用户都能或都不能在其中创建对象。

不需要共享创建能力时,可明确收紧:

REVOKE CREATE ON SCHEMA public FROM PUBLIC;

执行前要确认现有扩展、迁移工具和开发流程是否依赖该权限。

search_path 安全​

如果一个不受信任角色能在搜索路径前部的模式中创建对象,它可能创建与预期函数、操作符或表同名的对象,从而劫持未限定调用。

高权限函数和迁移脚本应:

  • 使用模式限定对象名;
  • 把 pg_catalog 和受信任业务模式放入固定路径;
  • 排除任何不受信任角色拥有 CREATE 的模式;
  • 特别审查 SECURITY DEFINER 函数。

例如:

CREATE FUNCTION admin.close_order(target_order_id BIGINT)
RETURNS VOID
LANGUAGE SQL
SECURITY DEFINER
SET search_path = pg_catalog, sales
AS $$
UPDATE sales.orders
SET status = 'closed'
WHERE order_id = target_order_id;
$$;

函数体仍应显式限定关键对象,并安全处理动态 SQL。

用模式组织项目​

常见方式包括:

按业务领域​

account.users
sales.orders
inventory.products

边界清晰,适合模块化单体或共享数据库中的多个服务,但跨模式外键与事务仍会形成耦合。

按数据生命周期​

app.events
archive.events

便于区分在线数据与归档数据。归档策略、权限和备份仍需单独设计。

按环境或测试隔离​

为短期测试创建独立模式能快速隔离对象,但不能替代数据库级资源隔离。连接数、系统目录、扩展和数据库参数仍然共享。

每租户一个模式​

“每个租户一个模式”可以提供清晰命名隔离,并便于单租户导出;但租户数量很大时会产生大量表、索引和迁移对象,增加系统目录膨胀、连接搜索路径管理、部署和监控成本。

常见方案对比:

方案优点主要成本
共享表加 tenant_id对象数量少、统一迁移依赖正确过滤、约束和 RLS
每租户一个模式逻辑隔离直观对象数量与迁移复杂度随租户增长
每租户一个数据库隔离最强、可独立运维连接、升级和资源管理成本更高

选择应基于租户数量、法规隔离、定制化程度、备份恢复和运维能力,而不是只看 SQL 查询是否简短。

移动与重命名对象​

移动表:

ALTER TABLE public.orders SET SCHEMA sales;

数据库内部依赖通常按对象身份继续关联,但应用中的文本 SQL、动态查询、权限和 search_path 可能失效。移动后要重新审查模式 USAGE 与默认权限。

重命名模式:

ALTER SCHEMA sales RENAME TO commerce;

这同样需要应用兼容和依赖审计。

常见误区​

模式等于独立数据库​

模式只提供命名和权限组织,不隔离连接、资源、数据库级设置和故障域。

设置 search_path 后永远不必限定名称​

搜索路径会随角色、连接和函数上下文变化。安全敏感或长期维护的 SQL 应优先显式限定。

模式 USAGE 会授予表读取权限​

不会。模式与对象权限需要分别授予。

public 模式和 PUBLIC 角色是同一个东西​

不是。前者是对象容器,后者表示所有角色。

模式检查清单​

  1. 模式边界是业务组织、权限隔离还是生命周期管理;
  2. 应用是否使用稳定、受控的 search_path;
  3. 高权限代码是否限定对象名并排除不受信任模式;
  4. public 的 CREATE 权限是否符合安全基线;
  5. 模式 USAGE 与对象权限是否都正确;
  6. 默认权限是否覆盖未来由正确角色创建的对象;
  7. 移动或重命名对象时是否检查文本 SQL 和外部工具;
  8. 多租户方案的对象数量和迁移成本是否可持续。

模式最有价值的地方是提供清晰的命名和授权边界。把 search_path 当成安全配置,而不只是省略前缀的便利功能,能避免很多隐蔽问题。