# 模式与 search_path

> 将数据库对象组织到模式中，并使用 `search_path` 控制名称解析。

---

LLMS 索引： [llms.txt](/llms.txt)

---

PostgreSQL 中的*模式*是数据库内部的命名空间——一种将表、视图、函数、序列和类型归到某个名称下的方式。每个数据库至少有一个模式（`public`），大多数生产部署会使用多个模式。模式可以帮助你组织大型项目、在一个数据库中承载多个租户，并将扩展与应用程序代码隔离开来。理解 `search_path` 后，也就理解了 PostgreSQL 如何解析非限定名。

## 本章要点 {#learn}

- 创建和删除模式
- `schema.table` 限定名语法
- `search_path` 以及名称解析的工作方式
- 按角色和按数据库设置的 `search_path` 默认值
- 常见模式：`app` 模式、每个租户一个模式、将扩展分离出来

## 示例 {#example}

```sql {num="23.1" caption="模式与 search_path"}
-- Create a schema and a table inside it
CREATE SCHEMA IF NOT EXISTS app;

DROP TABLE IF EXISTS app.events_example;
CREATE TABLE app.events_example (
  id   bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  kind text NOT NULL,
  at   timestamptz NOT NULL DEFAULT current_timestamp
);

INSERT INTO app.events_example (kind) VALUES ('login'), ('logout');

-- Fully qualified vs. unqualified access
SELECT * FROM app.events_example ORDER BY id;

-- Make the schema findable without prefixing every name
SET search_path TO app, public;
SELECT * FROM events_example ORDER BY id;   -- resolved as app.events_example

-- Inspect the current search path
SHOW search_path;

-- List schemas and the tables in each
SELECT schemaname, tablename
FROM pg_tables
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
ORDER BY schemaname, tablename
LIMIT 10;

-- Reset to default
SET search_path TO public;
```

当你写下 `SELECT * FROM events` 时，PostgreSQL 会按照 `search_path` 中列出的模式顺序，在每个模式中查找这个名称，并使用第一个匹配项。默认的 `search_path` 是 `"$user", public`——意思是“先查找一个以连接用户命名的模式，再查找 `public`”。如果存在一个与用户同名的模式，它就会优先匹配。

你可以按会话（`SET search_path TO ...`）、按角色（`ALTER ROLE alice SET search_path TO ...`）或按数据库（`ALTER DATABASE app_db SET search_path TO ...`）修改 `search_path`。后两种设置会在重新连接后继续生效。

模式隔离是将扩展对象（`postgis`、`pg_trgm`）放在 `public` 之外、为每个租户提供自己的命名空间，或按逻辑组织 `audit.*`、`app.*`、`staging.*` 表的标准方式。

## 运行示例 {#run}

运行方式：

```text
$ psql -f source/schemas.sql postgres
CREATE SCHEMA
DROP TABLE
CREATE TABLE
INSERT 0 2
 id |  kind  |              at
----+--------+-------------------------------
  1 | login  | 2024-xx-xx xx:xx:xx.xxxxxx+00
  2 | logout | 2024-xx-xx xx:xx:xx.xxxxxx+00
(2 rows)
 ...
 search_path
-------------
 app, public
(1 row)
 ...
```

## 常见陷阱 {#pitfalls}

- 在函数中忘记设置 `search_path`：`SECURITY DEFINER` 函数使用其所有者的权限，但如果没有函数级设置，它使用的是调用者的 `search_path`。请将路径限制为只包含受信任的模式，并把 `pg_temp` 放在最后（例如，在 `app` 受信任时，使用 `SET search_path = pg_catalog, app, pg_temp`），同时为敏感对象名称加上限定名。
- 把扩展放进 `public`：扩展会将对象安装到当前所在的模式中。许多团队会执行 `CREATE SCHEMA extensions; ALTER ROLE ... SET search_path = "$user", public, extensions;`，并在那里创建扩展。
- 删除存在依赖关系的模式会失败。使用 `DROP SCHEMA name CASCADE` 删除模式及其依赖对象。这次删除可以在事务中回滚；提交之后，恢复就需要备份或重新创建。
- 别忘了 `\dt` 只显示当前 `search_path` 模式中的表。使用 `\dt *.*` 查看全部内容。

> [!TIP] 提示
> 对于多租户系统，每个租户一个模式在小规模场景（几十到几百个租户）中可行，但随着系统目录增长会变得难以维护。行级隔离（使用 `tenant_id` 列和 `ROW SECURITY` 策略）是更常见的大规模模式。

> [!NOTE] 动手练习
> 在交互式 psql 中运行 `SHOW search_path;`，然后执行 `SET search_path TO app, public;` 和 `\dt`。在两个模式中创建同名表，看看哪个表会优先匹配。然后删除该模式：`DROP SCHEMA app CASCADE;`（只在可丢弃的演示模式中使用）。

## 下载 {#downloads}

SQL 文件：[schemas.sql](/examples/source/schemas.sql) · [全部示例文件](/examples/examples.zip) · [准备工作](/#prerequisites)
