在 PostgreSQL 中组织数据的一种方法是使用模式。PostgreSQL 中的模式是什么?更重要的是:模式的目的是什么以及如何使用模式让您的生活更轻松?让我们深入了解一下。

模式的目的

在弄清楚如何使用模式之前,您需要首先了解模式的用途。要理解这一点,首先看一下 PostgreSQL 的结构:

•实例 •数据库模式 •表 •列

“实例”基本上就是您在部署 PostgreSQL 时启动的。下一层是数据库。实际上,这就是您要连接的对象:在 PostgreSQL 中,连接始终绑定到实例内的数据库,这发生在用户身份验证之后的早期。

重要的是数据库和表之间的一层:模式

模式组表

基本上,模式是一种将表分组在一起的方法。

让我们假设有一个相当大的数据结构:在一个地方有 500 个表肯定比有 10 个桶,每个桶包含 50 个表更难管理和理解。

这就像组织图片一样:您不会将所有图片都放在同一个文件夹中,而是按年份、位置等对它们进行分组。相同的逻辑可以应用于表格。

模式和 PostgreSQL

现在我们可以关注如何将这个概念应用于 PostgreSQL。我们首先要看的是 public 模式。

使用 public 公共模式

PostgreSQL 的美妙之处在于,即使您对模式一无所知也没关系。原因是 public 模式的存在,默认情况下存在。我们如何找出 PostgreSQL 中有哪些模式?

psql 提供了\dn显示此信息的命令:

demo=# \dn
      List of schemas
  Name  |       Owner       
--------+-------------------
 public | pg_database_owner
(1 row)

在默认情况下,表将最终出现在public模式中。这是一个例子:

demo=# CREATE TABLE t_product (
id      serial, 
name    text, 
price   numeric
);
CREATE TABLE

这是一个基本表。该表可以在public模式中找到。\d将揭示真相:

demo=# \d
              List of relations
 Schema |       Name       |   Type   | Owner 
--------+------------------+----------+-------
 public | t_product        | table    | hs
 public | t_product_id_seq | sequence | hs
(2 rows)

在这种情况下,模式和序列都按预期在默认模式中找到。如您所见,您不需要任何关于模式的知识即可继续。如果您碰巧使用了该public模式,我们还建议您查看最新版本的 PostgreSQL 中引入的新安全策略。

两种查询表的方法

第一种方法是:

demo=# SELECT * FROM t_product;
 id | name | price 
----+------+-------
(0 rows)

查询表的另一种方法:

但是,您也可以显式地使用模式名称作为表名的前缀,这构成了一个完全限定的名称。能够降低由于配置错误而访问错误表的风险。我们稍后还会在这篇文章中看到它:

demo=# SELECT * FROM public.t_product;
 id | name | price 
----+------+-------
(0 rows)

在对 public 模式进行简要介绍之后,我们可以继续创建我们的第一个新模式。

创建模式

我们如何在 PostgreSQL 中创建模式?命令 CREATE SCHEMA 就是答案:

demo=# \h CREATE SCHEMA
Command:     CREATE SCHEMA
Description: define a new schema
Syntax:
CREATE SCHEMA schema_name 
[ AUTHORIZATION role_specification ] 
[ schema_element [ ... ] ]
CREATE SCHEMA AUTHORIZATION role_specification 
[ schema_element [ ... ] ]
CREATE SCHEMA IF NOT EXISTS schema_name 
[ AUTHORIZATION role_specification ]
CREATE SCHEMA IF NOT EXISTS AUTHORIZATION role_specification

where role_specification can be:

    user_name
  | CURRENT_ROLE
  | CURRENT_USER
  | SESSION_USER

语法非常简单,允许我们定义名称和模式所有者。

demo=# CREATE SCHEMA warehouse;
CREATE SCHEMA

创建模式后,我们可以在模式中创建一个表:

demo=# CREATE TABLE warehouse.t_product (
   prod_number text PRIMARY KEY, 
   d           date, 
   in_stock    int
);
CREATE TABLE

通过使用模式名称作为表名的前缀,您可以定义要使用的模式。请注意,模式本身不会影响数据的存储方式。与我们的表关联的数据文件仍然在同一个 PostgreSQL 数据目录中。因此,模式不会影响性能,也与存储优化无关。模式的目的只是将事物组合在一起,并通过为模式分配权限来帮助组织可靠的安全策略:

demo=# \d warehouse.t_product;
              Table "warehouse.t_product"
   Column    |  Type   | Collation | Nullable | Default
-------------+---------+-----------+----------+---------
 prod_number | text    |           | not null | 
 d           | date    |           |          | 
 in_stock    | integer |           |          | 
Indexes:
    "t_product_pkey" PRIMARY KEY, btree (prod_number)

这里有两点值得指出:

首先,在两个不同的模式中可以有两个同名的表。有名为public.t_product和warehouse.t_product表是完全可行的,而且实际上很常见。

然后,重要的是我们不必在public模式中为表添加前缀。原因是以下参数:

demo=# SHOW search_path;
   search_path   
-----------------
 "$user", public
(1 row)

可以直接访问要在search_path参数定义的模式名中的所有数据库对象,而无需显式提供模式名称。我们可以轻松地尝试一下:

demo=# SET search_path TO warehouse;
SET

请注意,该参数仅在您的会话中更改——如果您在交互式会话中运行它,它不会破坏您的生产系统。

从现在开始,将显示名为t_product的表,因为 PostgreSQL 知道在哪个模式中查找:

demo=# \d t_product
              Table "warehouse.t_product"
   Column    |  Type   | Collation | Nullable | Default
-------------+---------+-----------+----------+---------
 prod_number | text    |           | not null | 
 d           | date    |           |          | 
 in_stock    | integer |           |          | 
Indexes:
    "t_product_pkey" PRIMARY KEY, btree (prod_number)

现在search_path已经更改了,我们必须在模式前加上前缀public,因为它不再在路径中:

demo=# \d public.t_product
                                Table "public.t_product"
 Column |  Type   | Collation | Nullable |                   Default                   
--------+---------+-----------+----------+-----------------...
 id     | integer |           | not null | …
 name   | text    |           |          | 
 price  | numeric |           |          |

在对模式进行了基本介绍之后,让我们弄清楚将模式与视图结合使用意味着什么?

视图和模式

视图是让开发人员更容易访问数据的好方法。重要的一点是模式通常不是障碍(有关视图和权限的具体细节,请参见此处)。查询可以自由地连接来自不同模式的表,并且使用查询的视图可以在您选择的模式中公开数据(假设您有这样做的权限):

demo=# SET search_path TO default;
SET
demo=# CREATE VIEW public.v AS
SELECT * 
FROM warehouse.t_product ;
CREATE VIEW

但是,这对想要从 Oracle 迁移到 PostgreSQL 的人有影响。

在 PostgreSQL 中重命名模式

在 PostgreSQL 中,所有可以创建的东西都可以重命名。模式也是如此:

demo=# \h ALTER SCHEMA
Command:     ALTER SCHEMA
Description: change the definition of a schema
Syntax:
ALTER SCHEMA name RENAME TO new_name
ALTER SCHEMA name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER }

URL: ALTER SCHEMA

重命名模式会产生影响,这些影响在以下内容中进行了概述。注意视图发生了什么:

demo=# ALTER SCHEMA warehouse RENAME TO inventory;
ALTER SCHEMA
demo=# \d+ v
                                 View "public.v"
   Column    |  Type   | Collation | Nullable | Default | Storage  | …
-------------+---------+-----------+----------+---------+----------+ …
 prod_number | text    |           |          |         | extended | 
 d           | date    |           |          |         | plain    | 
 in_stock    | integer |           |          |         | plain    | 
View definition:
 SELECT t_product.prod_number,
    t_product.d,
    t_product.in_stock
   FROM inventory.t_product;

视图不直接引用表——它引用内部对象 ID,这在这里非常重要,因为重命名模式仅意味着将不同的文本标签附加到内部 ID。视图定义不依赖于名称,因此重命名对象确实会使视图无效。在 Oracle 等数据库中,重命名对象会使视图处于无效状态。

删除模式

在 PostgreSQL 中删除模式遵循相同的逻辑:

demo=# \h DROP SCHEMA
Command:     DROP SCHEMA
Description: remove a schema
Syntax:
DROP SCHEMA [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ]

URL: DROP SCHEMA

模式依赖

我们不需要孤立的对象,所以我们不能在没有附带损害的情况下丢弃它们。PostgreSQL 会准确告诉我们发生什么,但不会真正删除表——以避免破坏对模式的依赖:

demo=# DROP SCHEMA inventory;
ERROR:  cannot drop schema inventory because other objects depend on it
DETAIL:  table inventory.t_product depends on schema inventory
view v depends on table inventory.t_product
HINT:  Use DROP ... CASCADE to drop the dependent objects too.

如果我们真的想放弃模式并面对与之相关的所有后果,CASCADE可以添加该选项:

demo=# DROP SCHEMA inventory CASCADE;
NOTICE:  drop cascades to 2 other objects
DETAIL:  drop cascades to table inventory.t_product
drop cascades to view v
DROP SCHEMA

如您所见,所有依赖对象都已删除,我们留下了一个干净、一致的数据库,其中不包含任何陈旧或无效的对象。有关详细信息,请参阅关于视图依赖项的博客。

最后 …

即使您没有意识到,模式始终是重要的一部分;它们提供了一种以更易于理解的方式更清晰地组织数据的好方法。有关如何允许其他用户访问特定模式中的对象的更多信息,请参阅有关 ALTER DEFAULT PRIVILEGES 的文档。