PostgreSQL schema 和 search_path 是什么?多 schema 使用有什么坑?
简化版
PostgreSQL 的 schema 是数据库内的命名空间,search_path 决定未带 schema 前缀的对象名从哪里查找。多 schema 能做对象隔离,但要警惕对象名冲突、权限配置、SQL 注入式劫持和连接池 search_path 污染。
详细版
一个数据库里可以有多个 schema,例如 public、tenant_a、tenant_b。访问对象可以写 tenant_a.orders,也可以依赖 search_path。
search_path 类似查找路径:如果写 SELECT * FROM orders,PostgreSQL 会按路径顺序找第一个匹配表。
多 schema 适合模块隔离、插件隔离、轻量租户隔离。但生产中更推荐显式指定 schema 或严格控制 search_path,避免连接复用和同名对象带来的问题。
完整版教学
一、schema 是什么
PostgreSQL 的 database 下面还有 schema 层。schema 不是用户,也不是实例,而是对象命名空间。表、视图、函数都可以放在 schema 里。
database: appdb
schema: public
table: users
schema: audit
table: users
两个 schema 里可以有同名表,因为完整名称不同:public.users 和 audit.users。
二、search_path 如何工作
search_path 决定未限定对象名的查找顺序。
SET search_path TO tenant_a, public;
SELECT * FROM orders;
这条 SQL 会先找 tenant_a.orders,找不到再找 public.orders。如果两个地方都有 orders,会使用前面的那个。
三、为什么 search_path 可能危险
未限定对象名依赖环境。开发环境 search_path 是 public,生产连接池里某个请求设置成 tenant_a 后没恢复,下一次请求可能访问错 schema。
函数也有类似风险。如果攻击者能在靠前 schema 创建同名函数,未限定调用可能被劫持。这类问题在安全敏感系统里必须重视。
search_path = malicious, public
调用 foo() -> 先命中 malicious.foo()
四、多 schema 做租户隔离的取舍
每个租户一个 schema 可以让数据物理上按命名空间分开,迁移单租户数据也相对清楚。但租户很多时,schema、表、索引、迁移脚本数量都会膨胀。
| 方案 | 优点 | 缺点 |
|---|---|---|
| 单 schema + tenant_id | 简单、易扩展 | 隔离弱 |
| 每租户 schema | 隔离更清楚 | 运维复杂 |
| 每租户数据库 | 隔离强 | 成本更高 |
如果是几十个大租户,每租户 schema 可考虑;如果是几十万小租户,通常不适合。
五、权限和迁移要统一管理
多 schema 下,权限要明确授予到 schema 和对象。只给表授权不一定够,schema 的 USAGE 权限也很关键。
迁移脚本也要小心。DDL 应明确写 schema,避免把表建到错误位置。连接池初始化时要统一设置 search_path,或者应用 SQL 全部使用限定名。
一个实用原则是:核心业务 SQL 尽量显式写 schema.table,把 search_path 作为辅助配置,而不是核心路由机制。
六、常见误区与追问
- 误区:schema 等于一个独立数据库。 schema 是命名空间,仍共享同一个数据库资源。
- 误区:search_path 只是方便缩写。 它会影响对象解析,配置错误可能访问错表或函数。
- 误区:多 schema 做租户隔离一定最好。 租户数量大时迁移、权限和对象数量会很重。
- 追问:如何避免 search_path 污染? 显式限定对象名,或每个事务设置并清理 search_path。
- 追问:schema 权限要注意什么? 需要 schema USAGE 权限和对象级权限共同配合。
七、加强记忆
记忆钩子:schema 像楼层,search_path 像电梯默认停靠顺序;不写楼层号时,电梯先停哪层就进哪层。
回答这题时讲清命名空间、查找路径、同名冲突、连接池污染和多租户取舍。它看似基础,线上坑却非常实在。