📚 Research Summary
PostgreSQL 的 Schema 是一个常被低估的架构工具。它不仅仅是"命名空间",更是数据库层面职责分离、权限控制、多租户架构的核心载体。本文从第一性原理出发,探讨 Schema 设计的关键决策。
🌱 逻辑原点(The Origin)
两难境地:你的系统从单体演进到微服务,数据库表从 50 张增长到 500 张。
- 如果全部放在
publicschema,命名冲突频发:user表被 5 个服务各自定义- 如果按服务拆分 schema,权限管理、跨 schema 查询又成了新问题
核心问题:Schema 到底是"文件夹"还是"数据库中的数据库"?当你理解了它的本质,很多架构问题就有了答案。
🧠 苏格拉底式对话(Socratic Inquiry)
第一阶 - 现状:如果没有 Schema 会怎样?
提问 :PostgreSQL 默认的 public schema 有什么问题?
sql
-- 所有表都挤在 public 里
CREATE TABLE users (...); -- 用户服务
CREATE TABLE orders (...); -- 订单服务
CREATE TABLE products (...); -- 商品服务
CREATE TABLE audit_log (...); -- 审计服务
CREATE TABLE temp_import_data (...); -- 临时数据
你会发现的问题:
- 命名冲突:
user是保留字,users被多个业务实体共享 - 权限粒度粗:无法给不同服务分配独立的数据库权限
- 概念混淆:核心业务表和辅助表混在一起
- 迁移困难:无法按服务独立迁移数据
第二阶 - 瓶颈:当业务规模扩大会怎样?
提问:假设我们按服务拆分 schema:
sql
CREATE SCHEMA auth;
CREATE SCHEMA orders;
CREATE SCHEMA inventory;
CREATE SCHEMA analytics;
-- 每个服务管理自己的表
CREATE TABLE auth.users (...);
CREATE TABLE orders.orders (...);
CREATE TABLE inventory.products (...);
新的问题来了:
-
跨 Schema 查询 :订单服务需要关联商品信息,怎么写?
sqlSELECT * FROM orders.orders o JOIN inventory.products p ON o.product_id = p.id; -
搜索路径:每次都要写完整路径很麻烦
-
权限矩阵:每个 schema 需要单独配置权限
-
ORM 适配:很多 ORM 对多 schema 支持不佳
第三阶 - 突破:Schema 的真正价值
提问:Schema 到底能帮我们解决什么问题?
关键认知:
- Schema 是权限边界:可以为每个 schema 设置独立的所有权和访问控制
- Schema 是命名隔离:同名对象可以在不同 schema 中共存
- Schema 是多租户基础:每租户一个 schema 是经典的多租户模式
- Schema 是生命周期管理工具:可以批量删除一个 schema 的所有对象
📊 视觉骨架(Mermaid Visual)
Schema 作为架构边界的分层模型
PostgreSQL 实例
archive schema ⏰
历史订单归档
日志归档
analytics schema
events
metrics
reports
orders schema
orders
order_items
shipping
auth schema
users
sessions
permissions
public schema
系统配置表
公共字典表
认证服务
订单服务
分析服务
多租户 Schema 模式
租户隔离架构
只读
只读
shared schema
产品目录
系统配置
租户 B Schema
orders_b
users_b
租户 A Schema
orders_a
users_a
租户 A 应用
租户 B 应用
⚖️ 权衡模型(The Trade-off)
Schema 设计的代价公式
多 Schema 收益 = 权限隔离 + 命名清晰 + 多租户友好
- 查询复杂度(跨 schema join)
- 运维成本(更多对象管理)
- ORM 适配问题
| 维度 | 单 Schema(public) | 多 Schema(按服务/租户) |
|---|---|---|
| 命名冲突 | 高风险 | 低风险 |
| 权限管理 | 粒度粗 | 粒度细 |
| 跨表查询 | 简单 | 需要 schema 前缀 |
| 多租户隔离 | 需要额外字段 | 天然隔离 |
| ORM 兼容性 | 完美 | 需要额外配置 |
| 备份恢复 | 整体备份 | 可按 schema 备份 |
🔁 记忆锚点(Mental Model)
Schema 的三种角色模型
typescript
// Schema 作为命名空间
interface NamespaceRole {
purpose: "避免命名冲突";
pattern: "CREATE SCHEMA service_x; CREATE TABLE service_x.users";
when: "表数量 > 100 或多团队协作";
}
// Schema 作为权限边界
interface PermissionBoundary {
purpose: "最小权限原则";
pattern: "GRANT ALL ON SCHEMA orders TO orders_service";
when: "多服务共享数据库";
}
// Schema 作为租户隔离
interface TenantIsolation {
purpose: "数据隔离";
pattern: "CREATE SCHEMA tenant_123; SET search_path TO tenant_123";
when: "SaaS 多租户架构";
}
🎯 决策树
是
否
否
是
是
否
是
否
数据库设计
多租户需求?
每租户一个 Schema
使用 search_path 动态切换
共享数据放 shared schema
服务数量 > 3?
表数量 < 50?
服务独立部署?
单 Schema 足够
按领域拆分
每服务一个 Schema
✅ 简单项目、快速迭代
✅ 领域驱动设计、适度复杂度
✅ 微服务架构、独立权限控制
✅ SaaS 平台、强隔离需求
💡 实战最佳实践
1. 命名规范
sql
-- ✅ 推荐:小写 + 下划线,语义清晰
CREATE SCHEMA auth_service;
CREATE SCHEMA order_management;
CREATE SCHEMA analytics_reporting;
-- ❌ 避免:驼峰、特殊字符、保留字
CREATE SCHEMA "authService"; -- 需要引号,麻烦
CREATE SCHEMA "Order-Service"; -- 连字符,更麻烦
CREATE SCHEMA user; -- 保留字风险
2. 搜索路径设置
sql
-- 应用层设置默认搜索路径
SET search_path TO auth_service, public;
-- 在函数中设置
CREATE FUNCTION auth_service.get_current_user()
RETURNS SETOF users AS $$
BEGIN
-- 函数内的查询自动使用 search_path
RETURN QUERY SELECT * FROM users;
END;
$$ LANGUAGE plpgsql;
3. 权限控制模式
sql
-- 创建服务专用角色
CREATE ROLE orders_service_role;
CREATE ROLE auth_service_role;
-- 授予 schema 权限
GRANT ALL ON SCHEMA orders TO orders_service_role;
GRANT ALL ON SCHEMA auth TO auth_service_role;
-- 只读权限给分析服务
GRANT USAGE ON SCHEMA orders TO analytics_service_role;
GRANT SELECT ON ALL TABLES IN SCHEMA orders TO analytics_service_role;
4. 多租户 Schema 模板
sql
-- 创建租户 schema 的模板函数
CREATE OR REPLACE FUNCTION create_tenant_schema(tenant_id TEXT)
RETURNS void AS $$
DECLARE
schema_name TEXT := 'tenant_' || tenant_id;
BEGIN
EXECUTE format('CREATE SCHEMA %I', schema_name);
-- 复制表结构
EXECUTE format('CREATE TABLE %I.users (LIKE template.users INCLUDING ALL)', schema_name);
EXECUTE format('CREATE TABLE %I.orders (LIKE template.orders INCLUDING ALL)', schema_name);
-- 设置权限
EXECUTE format('GRANT ALL ON SCHEMA %I TO tenant_%s_role', schema_name, tenant_id);
END;
$$ LANGUAGE plpgsql;
5. 版本管理 schema
sql
-- 数据库迁移版本隔离
CREATE SCHEMA v1; -- 老版本表
CREATE SCHEMA v2; -- 新版本表
-- 灰度发布时双写
INSERT INTO v1.orders (...) VALUES (...);
INSERT INTO v2.orders (...) VALUES (...);
🚨 常见陷阱
陷阱 1:忘记设置 search_path
sql
-- ❌ 问题:找不到表
SELECT * FROM users; -- ERROR: relation "users" does not exist
-- ✅ 解决:设置搜索路径
SET search_path TO auth_service, public;
SELECT * FROM users; -- 现在可以找到 auth_service.users
陷阱 2:跨 Schema 外键
sql
-- ⚠️ 可以但需谨慎
ALTER TABLE orders.orders
ADD CONSTRAINT fk_user
FOREIGN KEY (user_id) REFERENCES auth.users(id);
-- 考虑:这会让 schema 之间产生耦合
-- 替代方案:应用层校验 + 定期数据一致性检查
陷阱 3:Schema 数量过多
sql
-- ⚠️ 多租户场景下,租户数量 > 10000 时
-- 每租户一个 schema 可能导致:
-- 1. 系统表膨胀
-- 2. 备份恢复变慢
-- 3. 连接池管理复杂
-- 替代方案:分片 + schema 混合
-- tenant_0001 ~ tenant_1000 → schema tenant_group_1
-- tenant_1001 ~ tenant_2000 → schema tenant_group_2
📖 Sources
- PostgreSQL Official Documentation - Schemas: https://www.postgresql.org/docs/current/ddl-schemas.html
- PostgreSQL Multi-tenant Architecture: https://www.postgresql.org/docs/current/ddl-schemas.html#DDL-SCHEMAS-PATTERNS
- "PostgreSQL: Up and Running" - Regina Obe & Leo Hsu (2024)
- Schema Design Best Practices - PostgreSQL Wiki: https://wiki.postgresql.org/wiki/Schema_design
- Multi-tenant Data Architecture: https://docs.microsoft.com/en-us/azure/architecture/guide/multitenant/considerations
一句话总结:
Schema 不是文件夹,而是数据库层面的"微服务边界"。用对了,它是权限隔离和多租户的利器;用错了,就是给自己挖坑。记住:少即是多,按需拆分。