全面解读 PostgreSQL 18 新功能

全面盘点 PostgreSQL 18 的新特性、性能优化、运维与可观测性改进,以及面向开发者的新工具。

   Postgres/PostgreSQL | 

全面盘点 PostgreSQL 18 的新特性、性能优化、运维与可观测性改进,以及面向开发者的新工具。

上周,Postgres 18 正式发布稳定版。其亮点包括:异步 I/O 基础设施和 OAuth 2.0 支持等基础特性,B-树跳过扫描等性能优化,以及原生 UUIDv7 支持等备受期待的功能。

但本次发布涉及超过 3000 次提交,除核心亮点外还有诸多值得关注的变更。我们力求全面梳理变更内容,最终在这篇长文(近5000字)中涵盖了约30项功能。

若您无暇通读全文,我完全理解——以下是简要提要:

  • 异步IO是极具前景的根本性变革,但目前仅适用于特定场景,因此您的使用场景可能尚未体现其全部优势。
  • 部分默认行为有所调整(如生成列的VIRTUAL属性),但整体升级过程应较为顺畅,对应用程序代码的影响微乎其微。
  • 以下各领域均有重大改进:
    • 开发便利性(uuidv7、RETURNING old/new、虚拟生成列、时态数据库)。
    • 常规操作(更快的升级流程、oauth认证、vacuum优化、扩展管理)。
    • 特定场景的性能优化(B树索引、规划器改进)。
    • 可观测性/监控(进程级统计、EXPLAIN改进)。

不过我建议有时间的话还是通读全文,其中包含许多有趣的细节。我们还附上了代码示例和提交信息的片段。

元素周期表

异步I/O

在此版本之前,Postgres采用同步I/O(类似read()write()系统调用),依赖操作系统隐藏同步I/O的开销。它确实使用了posix_fadvise系统调用,该调用向操作系统“提示”应用程序计划如何访问文件。通过引入异步IO(AIO),Postgres获得了两大核心优势,我将其总结自 AIO读我文件总结如下:

  • 更直接地控制并行发送IO请求的时机,最大限度减少IO等待
  • 支持直接I/O(Direct IO),可通过DMA在存储设备与Postgres缓冲池间进行数据复制,从而避免占用CPU资源

这些改进最终将带来更优性能和更低资源消耗。对于支持高度并行性的网络附加存储设备,其效益尤为显著。在Xata,我们采用光纤NVMe技术——这正是AIO的理想应用场景,因此对此功能格外期待。

虽然AIO框架已引入,但尚未全面应用,因此当前需合理管理预期。托马斯·冯德拉的这篇博客文章提供了我所见过的最佳性能概述。

简而言之:

  • 顺序扫描性能提升2-3倍!
  • 索引扫描未见改善。
  • 位图扫描虽有提升,但在io_workers值较低时会出现性能退化

此外,AIO应已降低后台维护任务的开销。

OAuth 2.0

OAuth支持意味着现已存在避免共享长期密码的标准化解决方案。您可通过单点登录提供商获得交互式Postgres访问权限,应用程序也能通过OAuth工作流与Postgres进行身份验证。

实际应用中,交互式登录的Postgres连接字符串可能如下所示:

$ psql 'host=example.org oauth_issuer=https://... oauth_client_id=...'
Visit <https://oauth.example.org/login> and enter the code: FPQ2-M4BG

应用程序可通过如下连接字符串使用令牌:

host=example.com oauth_issuer=https://... oauth_client_id=... oauth_token=...

我们计划近期在 Xata 中采用此方案替代静态密码。

开发者优化

若您以开发者身份使用 PostgreSQL,本节内容将最符合您的需求。

UUID v7

如何生成主键是开发者最热衷的讨论话题之一,如今终于有了简便的解决方案。在Postgres 18之前,开发者可使用原生UUID类型中的UUID v4,但这会带来性能代价——由于UUIDv4不具备单调性,既影响索引局部性,又难以压缩。而UUIDv7完美解决了这些问题:它支持排序,并在开头嵌入了当前时间戳。

由于UUIDv7规范允许实现细节存在灵活性,以下是Postgres的具体实现方式(摘自提交信息):

在我们的实现中,12位亚毫秒级时间戳小数部分紧接在时间戳之后存储,位于RFC中称为“rand_a”的空间。这确保了毫秒级内的额外单调性。rand_ a位同时充当计数器。我们选择亚毫秒级时间戳,是为了确保在同一后端内生成的UUID具有单调递增特性——即使系统时钟倒转或以极高频率生成UUID时亦然。因此同一后端内生成的UUID具有单调性。

基本用法如下所示:

select uuidv7();

                uuidv7
--------------------------------------
 01997f39-9e31-7988-a86a-622879771a69
(1 row)

该函数支持间隔类型参数,可用于获取带有过去或未来时间戳的UUIDv7。例如,以下代码将生成带有昨日时间戳的UUIDv7:

select uuidv7(INTERVAL '-1 day');

                uuidv7
--------------------------------------
 01997a17-44c8-7bb3-a447-0d224f3de52f
(1 row)

您可以通过提取嵌入的时间戳进行验证,如下所示:

SELECT uuid_extract_timestamp('01997a17-44c8-7bb3-a447-0d224f3de52f');

   uuid_extract_timestamp
----------------------------
 2025-09-24 04:59:29.352+00
(1 row)

RETURNING 子句现可指定 OLD/NEW

作为开发者,这可能是我最喜爱的功能,因为它能大幅减少代码量和复杂度。该特性使RETURNING子句可通过特殊别名oldnew显式返回旧值和/或新值。除非查询中已定义这些别名,否则它们会自动添加到查询中。

以下是一个简短的UPDATE示例:

UPDATE foo SET name=upper(name) 
	RETURNING old.name as old_name, new.name as new_name;
	
 old_name | new_name
----------+----------
 foo      | FOO
(1 row)

虽然上述UPDATE是最典型的应用场景,但该功能同样适用于INSERT ... ON CONFLICT ... DO UPDATE等操作。

虚拟生成列:读取时计算

Postgres 18新增了一种生成列变体:VIRTUAL。这类列在读取时计算(类似视图),与存储生成列(在写入时计算,类似物化视图)不同。

以下是一个简短示例:

CREATE TABLE users (
    id serial PRIMARY KEY,
    first_name text NOT NULL,
    last_name text NOT NULL,
    full_name text 
      GENERATED ALWAYS AS (first_name || ' ' || last_name) VIRTUAL
);

需注意:VIRTUAL 现已成为默认行为,因此若从旧版 Postgres 恢复模式,生成列将从存储型自动切换为虚拟型。将VIRTUAL设为默认值的动机详见提交信息

拟视图默认采用VIRTUAL而非STORED模式,以兼容其他SQL产品。(SQL标准对此未作规定,且该标准本身也不认识VIRTUAL或STORED概念。)(此外,虚拟视图默认启用,而非物化视图。)

另需注意提交信息中的说明:这些视图在读取时进行计算,但存储成本并非完全为零:

虚拟生成列以空值形式存储于元组中。(早期补丁曾试图完全不存储这些列,但若元组中某列完全缺失,将导致大量功能失效或混乱。此方案虽属折中,仍比强制使用存储生成列更节省空间。若未来找到优化方案,可通过pg_upgrade的巧妙设计实现向新版方案的升级。)

逻辑复制中包含生成列

关于生成列,Postgres 18新增了将其纳入逻辑复制流的功能。此前默认假设从节点可重新生成这些列,但如今逻辑复制的应用场景已远超Postgres内部复制。

这对pgstream和Debezium等CDC工具是重大利好,它们现在能获取生成列数据。

时态数据库增强

首先,让我们快速定义时序数据库:这类数据库专门追踪随时间变化的数据。与仅存储最新信息状态不同,时序数据库不仅记录数据的当前值,还完整保存数据在其生命周期中的历史演变轨迹。

Postgres 18 增强 通过支持主键和唯一约束的 WITHOUT OVERLAPS 子句,增强了时序数据库的应用场景。此举通过强制执行关键时间规则——相同业务键对应的有效时间段不得重叠——使 Postgres 更接近时间数据库能力。

我们正在撰写关于将 PostgreSQL 用作时间数据库的深度博客文章,现提供简要示例:

-- loading this extension is required for the index to work
CREATE EXTENSION btree_gist;

CREATE TABLE bookings (
  room_id   int        NOT NULL,
  during    tstzrange  NOT NULL,
  -- Temporal PK: last column uses WITHOUT OVERLAPS
  PRIMARY KEY (room_id, during WITHOUT OVERLAPS)
);

上述主键确保同一房间在任何时间点均不会被重复预订。

此外,外键约束现可通过PERIOD关键字引用时间段。此功能支持范围型和多范围型数据类型。时间外键采用范围包含性而非相等性进行校验。

创建带NOT VALID约束的NOT NULL限制

此变更将NOT NULL约束添加到可作为NOT VALID添加的约束列表中。这意味着什么?假设您有一个当前包含NULL值的列。您不能直接添加NOT NULL约束,因为这会导致约束无效(且Postgres验证所有值期间会锁定表)。若先回填数据清除NULL值,则存在新插入操作可能再次添加NULL值的风险。

NOT VALID约束允许以下操作:

  • NOT VALID形式添加约束。此操作快速高效,因其不检查现有行。但此后插入操作必须为该列指定非空值。
  • 批量导入数据以清除所有NULL值。
  • 验证约束。此操作无需锁定表进行读写(技术上仍存在锁,但不阻塞读写操作)。

以下为操作示例:

CREATE TABLE foo(id int PRIMARY KEY, name text);
INSERT INTO foo(id) VALUES (1);
-- there is one row with name = NULL
ALTER TABLE foo ADD CONSTRAINT name_not_null NOT NULL name NOT VALID;

-- the following will fail
INSERT INTO foo(id) VALUES (2);

UPDATE foo SET name='';
ALTER TABLE foo VALIDATE CONSTRAINT name_not_null;

分区表上的 NOT VALID 外键约束

模式变更操作再获胜绩,其优势与上述类似:分区表的外键约束可声明 NOT VALID。例如,若events为分区表且数据引用accounts表时,以下操作可行:

ALTER TABLE events
  ADD CONSTRAINT events_account_fk
  FOREIGN KEY (account_id) REFERENCES accounts(id)
  NOT VALID;

验证可按分区逐个检查,这有助于进一步减少锁定。

新协议版本

Postgres 18 自2003年以来首次提升传输协议版本!这也是首次增加次要版本号。新版本号为3.2,其说明阐述了版本升级的原因:

查询取消机制中使用的密钥长度从4字节扩展为可变长度字段。为适应此变更,BackendKeyData消息结构已调整,CancelRequest消息亦重新定义为可变长度有效负载。

若您好奇为何版本号为3.2而非3.1,答案就在上述链接页面中:

保留版本号。尽管PostgreSQL从未使用过3.1版本,但因流行应用pgbouncer的旧版本存在协议协商漏洞——该漏洞导致其错误声称支持3.1版本——故跳过此版本号。

libpq客户端库目前仍默认使用3.0版本,直至上层组件(如驱动程序、连接池管理器、代理)添加对新协议版本的支持。鉴于此情况以及新版本的破坏性变更较小,我们不应因新版本引发兼容性问题。

操作改进

重大版本升级加速

重大版本升级是Xata团队持续投入的另一重点领域。Postgres 18在此方面带来多项显著改进。

首先,pg_upgrade整体运行速度将提升,尤其在同一集群中存在大量数据库,或更普遍地存在海量对象(表、视图、序列等)时。这得益于其新增的并行多任务框架,以及更智能地规避冗余操作和fsync的机制。

其次,pg_upgrade现可迁移旧版本的统计数据,这意味着升级后规划器能立即获取关键信息以实现最优执行,从而降低升级后的性能风险。

最令人称道的是,统计数据转移实际由pg_dump实现——该工具新增了--no-statistics--statistics-only选项。这使其在其他场景同样适用,例如通过逻辑复制实现蓝绿部署。

更便捷的K8s环境扩展管理

新增了一个名为extension_control_ path的配置选项,用于控制Postgres搜索扩展程序的路径。该功能由CloudNativePG项目提出,旨在为Kubernetes运维人员实现更便捷的声明式扩展管理。

此前由于镜像必须不可变,唯一可行的方案是构建包含所需扩展子集的定制镜像。现在则可使用精简镜像,通过挂载包含其他扩展的镜像卷实现扩展管理。

Xata将充分利用此功能,最终实现更强的安全性与更灵活的新扩展添加能力。

VACUUM优化

Postgres 18 带来了多项与 vacuum 相关的改进。

此项改进 降低了激进 vacuum 的执行成本。当Postgres检测到事务回绕风险时,便需要执行激进真空操作——此时系统会加速冻结旧元组(行数据)。

为摊销激进真空操作的成本,Postgres 18 在常规真空过程中会主动扫描部分可见但未冻结的页面。这意味着常规真空时工作量增加,但能显著避免最坏情况的发生。

Postgres 18 还更改 插入阈值的计算方式,使其不再包含冻结页,这意味着在插入密集型表中通常会更频繁地执行该操作。

真空操作的可观察性也得到提升,新增名为track_cost_ delay_timing的配置项,用于收集基于成本的真空延迟计时统计数据。请注意该参数默认关闭,因为它会反复查询操作系统获取当前时间,在某些平台上可能造成显著开销。所幸Postgres自带便捷工具pg_ test_timing,可帮助您判断在当前架构上启用该功能是否合理。

可观测性/监控改进

EXPLAIN 语句优化

Postgres 18 对 EXPLAIN 语句进行了多项细微改进。

值得注意的是,在执行 EXPLAIN ANALYZE 时,BUFFERS 现已成为默认值。提交 消息(双关语)阐述了更改默认值的理由:

过去几年中,多次出现关于在ANALYZE选项下启用EXPLAIN的BUFFERS选项的讨论。从多方面来看,此举似乎是个好主意,因为它能更直观地向用户说明特定查询为何运行速度低于预期。 此外,根据我(David)的个人经验,曾见过用户在邮件列表中贴出两个完全相同的执行计划——一个运行缓慢,一个运行迅速——并询问为何查询有时会变慢。多数情况下,这源于额外的读取操作。默认启用BUFFERS或许能减少此类疑问,即便不能,也能让用户在发帖前更清晰地意识到问题根源,避免因额外I/O操作导致的延迟而徒增邮件往来。

除上述改进外,EXPLAIN还提供了更丰富详尽的信息:包含Material节点的内存/磁盘使用情况、索引搜索次数、禁用节点数量等。

pg stat statements中的更多语句

另一项有助于可观测性的改进:CREATE TABLE ASDECLARE CURSOR 等语句现在会为其创建的内部查询分配查询 ID。此举的益处在于,这些查询将出现在 pg _stat_ statements 等视图中——因为该视图需要查询 ID 作为标识。

锁获取失败日志记录

此变更引入了新配置参数:log_lock_ failure。启用该参数(默认关闭)后,锁获取失败时将生成详细日志信息。目前仅支持记录由SELECT ... NOWAIT引发的锁获取失败。

日志包含所有持有或等待未获取锁的进程信息,有助于用户分析诊断锁获取失败的原因。

进程级统计功能

本次变更优化了统计基础设施,支持在进程生命周期内记录进程级统计数据。提交信息说明了其工作原理:

这在pgstats中新增了一种可变数量的统计类型,其统计条目的对象ID键基于后端的进程编号。该机制为同时存在的统计条目数量设定了上限。条目在后端通过身份验证启动时创建,并在后端退出时删除,因此统计条目仅在对应后端运行期间存在。

该新架构的首个用户是一个新函数:pg _stat_ get _backend_ io(),用于收集特定后端/进程的I/O统计数据。使用示例:

SELECT *
   FROM pg_stat_get_backend_io( pg_backend_pid() )
    WHERE backend_type = 'client backend'
      AND object = 'relation'
      AND context = 'normal';
-[ RECORD 1 ]--+---------------
backend_type   | client backend
object         | relation
context        | normal
reads          | 122
read_time      | 0
writes         | 0
write_time     | 0
writebacks     | 0
writeback_time | 0
extends        | 49
extend_time    | 0
op_bytes       | 8192
hits           | 11049
evictions      | 0
reuses         | 
fsyncs         | 0
fsync_time     | 0
stats_reset    | 

追踪连接建立时长

Postgres 18新增日志记录功能,可追踪从建立连接、配置后端直至连接准备就绪执行首条查询的耗时。日志包含三项时长:

  • 总配置时长(从postmaster接受连接请求起,至连接可执行查询结束)
  • 新后端进程分叉耗时
  • 用户身份验证耗时

启用该功能需在log_connections配置参数中添加setup_ durations选项。

性能提升与优化

索引优化:B-树跳跃扫描

假设你有一个多列索引,例如 (col1, col2, col3)。在 Postgres 18 之前,只有当条件中包含最左侧的列时,此类索引才能高效使用。因此以下查询都会使用索引:

SELECT * FROM foo WHERE col1 = '...';
SELECT * FROM foo WHERE col1 = '...' AND col2 = '...';
SELECT * FROM foo WHERE col1 = '...' AND col2 = '...' AND col3 = '...';

但以下查询通常不会使用索引:

SELECT * FROM foo WHERE col2 = '...';
SELECT * FROM foo WHERE col2 = '...' AND col3 = '...';

这是因为多列索引按元组 (col1, col2, col3) 的顺序存储键值,因此可利用其任意前缀进行检索。

Postgres 18 能够在后两种示例中高效利用索引。其工作原理是跳转至 col1 的值,并读取索引每个“分区”的相关部分。

col1 的基数较低时,这种方法效果更佳,因为此时可跳过更多数据。因此在定义多列索引时,将基数较低的列置于首位更为合理。

以下是相关文档提交记录中的关键段落:

教导nbtree多列索引扫描在遇到查询时,若该查询对一个或多个前缀索引列未设置“=”条件,则可选择性跳过索引中无关的部分。当nbtree接收源自谓词“WHERE b = 5”的输入扫描键时,新的nbtree预处理步骤会输出“WHERE a = ANY(<所有可能的’a’值>) AND b = 5”的扫描键。即预处理为被忽略的前缀列“a”生成“跳过数组”(及输出扫描键),从而可安全地将“b”列扫描键标记为继续扫描所需。扫描过程因此能通过同时应用“a”和“b”键反复重新定位。[…] 测试表明,当索引包含基数较低的跳过前缀列时,跳过扫描的速度可比等效的全索引扫描(或顺序扫描)快多个数量级。通常,扫描跳过列的基数决定了可跳过的叶页数量上限。

SQL语言函数使用计划缓存

这有助于更好地内联SQL函数中的查询。摘自提交信息:

在历史版本的 SQL 函数实现中(若未被内联),我们会在外部查询首次调用时为所有嵌套查询构建执行计划,随后在外部查询执行期间复用这些计划,最后彻底清除所有计划。这种方式并不理想,尤其因为计划无法根据函数参数的具体值进行定制。

自连接消除

自连接消除(SJE)功能可在查询树中移除普通表与其自身的内部连接,前提是该连接可被全表扫描替代且不影响查询结果。

此优化通过减少冗余形式,本质上能提升规划器的估算精度,并减轻后续层级的工作负荷。分区表尤其受益于此,因为它能更早识别分区修剪的潜在机会。

利用唯一索引检测冗余的GROUP BY列

当用户对多列唯一索引执行GROUP BY操作时,此规划器优化将生效。在此特殊情况下,Postgres 仅需使用其中一列即可,因为唯一索引确保了分组结果的等效性。

以下示例展示了该优化的效果:

CREATE TABLE employees (
    emp_id    serial PRIMARY KEY,
    dept_id   int NOT NULL,
    email     text NOT NULL,
    UNIQUE (dept_id, email)
);

SELECT dept_id, email
   FROM employees
   GROUP BY dept_id, email;

Postgres此前已对主键实施此优化,现将其扩展至所有多列唯一键。需注意:唯一索引中的列必须标记为NOT NULL,或索引必须采用NULLS NOT DISTINCT模式。

重排DISTINCT值以减少排序操作

当在多个列上使用DISTINCT时,这些列在DISTINCT子句中的顺序并不重要,因此优化器可以重新排序 进行重新排序,以最符合其需求的方式排列。以下示例说明此行为:

CREATE TABLE sales (
  store_id  int,
  sale_date date,
  amount    numeric
);

-- Note: index orders rows by (store_id, sale_date)
CREATE INDEX ON sales (store_id, sale_date);

-- Query: DISTINCT keys appear as (sale_date, store_id) in this order
-- Semantically it’s the same set of pairs either way.
SELECT DISTINCT sale_date, store_id FROM sales;

此行为现为默认设置,但可通过新配置项禁用:enable_distinct_ reordering

尽可能将’x IN (VALUES …)’转换为’x = ANY …’

此优化的核心在于简化查询树结构,消除冗余连接的出现。以下是典型场景:

EXPLAIN (ANALYZE, COSTS OFF)
  SELECT o.*
    FROM orders o
    WHERE o.id IN (VALUES (101), (205), (99999), (123456));

                                      QUERY PLAN
---------------------------------------------------------------------------------------
 Index Scan using orders_pkey on orders o (actual time=0.010..0.010 rows=0.00 loops=1)
   Index Cond: (id = ANY ('{101,205,99999,123456}'::integer[]))
   Index Searches: 1
   Buffers: shared hit=8
 Planning:
   Buffers: shared hit=26 read=1
   I/O Timings: shared read=0.019
 Planning Time: 0.178 ms
 Execution Time: 0.027 ms

注意执行计划中提及的ANY条件。因此等效SQL为:

SELECT o.*
  FROM orders o
  WHERE o.id = ANY('{101,205,99999,123456}'::integer[]);

提交信息解释了加速原理:

由于VALUES描述的是关系表,且此类列表的值为表行,优化器可能因无法通过MCV统计量估算基数而面临低估问题。基数评估机制可配合数组包含检查操作运行。若数组足够小(<100个元素),则会逐元素执行统计评估。

字符大小写转换

Postgres 18 新增了 casefold() 函数,该函数与 lower() 类似,但能避免无区分大小写匹配中的边缘案例问题。对于支持该功能的排序规则,casefold() 可处理具有超过两种大小写变体或多字符大小写变体的字符。

以下示例(摘自邮件列表)展示了大小写折叠比转换为小写更优的边界情况:

  • 某些字符存在超过两种大小写形式,例如“Σ”(U+03A3)可转换为小写“σ”(U+03C3)或“ς”(U+03C2)。casefold()函数将该字符的所有大小写形式统一转换为“σ”。
  • 字符“İ”(U+0130,带点的首字母I)会被转换为小写“i”,这在不支持此转换的区域设置中可能引发问题。
  • 若Unicode新增小写字符,lower()的处理结果可能随之改变。

更高效的lower()与upper()

与上述内容相关,Postgres 18对lower()upper()的实现也进行了加速优化。优化主要体现在映射表的生成方式,具体优势如下(摘自邮件列表):

  • 移除了所有映射表中存储Unicode码点(无符号整数)的功能。
  • 主表记录数从3003条精简至1575条(已消除重复项)。
  • 在主表中将指针(本质上是 uint64_t)替换为 uin8_ t。
  • 缩短了在表中查找记录的时间。
  • 缩小最终对象文件的体积。

提交信息中记载了其他备选方案:

曾考虑过其他方案,例如将这些区间表示为另一种结构(而非生成函数中的分支),或采用基数树、完美哈希等不同方法。作者实现并测试了这些替代方案后,最终选择了生成分支方案。

加速范围型 GiST 索引构建

GiST 支持“排序构建”模式:当输入元组已排序时,可显著提升树结构构建速度并优化压缩率。但要高效排序范围数据,规划器/执行器需要专用的排序支持函数。此功能通过该提交实现。

数组便捷函数

Postgres 18 引入了两个实用的数组便捷函数:array _reverse()array_ sort()

快速示例:

SELECT array_reverse(ARRAY[1,2,3]);

 array_reverse 
---------------
 {3,2,1}
(1 row)

 SELECT array_sort(ARRAY[3,1,2]);
 
 array_sort 
------------
 {1,2,3}
(1 row)

json_strip_ nulls() 可移除数组中的空值元素

JSON函数json_strip_ nulls()新增了一个参数:strip _in_ arrays。其默认值为false。当该参数为 true 时,不仅会移除数组中的 null 元素,还会移除对象字段中的 null 值。仅包含单个 null 的 JSON 数据不受影响。

添加获取数据库对象访问控制列表的函数

Postgres 18 引入新函数 pg_get_ acl(),用于检索和检查数据库对象关联的权限。示例如下:

postgres=# CREATE TABLE foo (id INT);
CREATE TABLE

postgres=# CREATE ROLE bar;
CREATE ROLE

postgres=# GRANT SELECT ON foo TO bar;
GRANT

postgres=# CREATE ROLE baz;
CREATE ROLE

postgres=# GRANT UPDATE ON foo TO baz;
GRANT

postgres=# SELECT unnest(pg_get_acl('pg_class'::regclass, 'foo'::regclass, 0));
           unnest           
----------------------------
 postgres=arwdDxtm/postgres
 bar=r/postgres
 baz=w/postgres
(3 rows)

从上例可见,bar角色获得读取权限,而baz角色获得写入权限。

你也许感兴趣的:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注