Postgres 18 的新功能

深入探究 Postgres 18 的功能

PostgreSQL 18 新功能、性能优化、操作和可观察性改进以及开发人员新工具的综合列表。

上周,Postgres 18 已标记为稳定版本。其亮点 包括 异步 IO 架构和 Oauth 2.0 支持等基础功能、btree skip scan 等性能优化,以及备受期待的原生 UUIDv7 支持等功能。

但是,由于本次发布有超过 3000 次提交,除了这些亮点之外,还有很多其他变化,您可能想了解一下。我们计划尽可能多地介绍这些变化,最终在这篇长文(近 5000 字)中涵盖了大约 30 个功能。

如果你根本没法读完这些,所以这里尝试做一个 TL;DR

  • 异步 IO 是一个具有很大前景的根本性变化,但目前它仅在某些情况下使用,因此其全部好处可能尚未在您的用例中显现出来。
  • 默认行为有一些变化(例如,生成列的 VIRTUAL),但总的来说,它应该是一个简单的升级,不会(或极小)影响您的应用程序代码。
  • 以下每类都有大量改进:
    • 开发人员便利(uuidv7、RETURNING old/new、virtual generated columns、temporal db)。
    • 一般操作(faster upgrades、oauth、vacuum、extension management)。
    • 针对特定情况的性能优化(btree 索引、规划器改进)。
    • 可观察性/监控(每个进程的统计数据,EXPLAIN 改进)。

不过,如果你有时间,我建议你完整地读一遍,因为里面有很多有趣的细节。我们附上了代码示例和提交信息中的片段。

Async IO

在此版本之前,Postgres 使用的是同步 IO (例如 read()write() 系统调用),并依赖操作系统缓存来隐藏同步 IO 的成本。它确实使用了 posix_fadvise 系统调用,它会向操作系统"提示"应用程序计划如何访问文件。通过引入异步 IO(AIO),Postgres 获得了两个主要好处,我从 AIO 的 readme 文件中总结了这些好处:

  • 更直接地控制并行和更早地发送 IO 请求,以最大限度地减少 IO等待。
  • 支持 Direct IO,可以使用 DMA 在存储和 Postgres 缓冲池之间进行复制,因此不会让 CPU 保持繁忙

这些最终将带来更佳的性能和更低的资源利用率。预计这对于支持高并行度的网络附加存储 (NAS) 尤其有利。在 Xata,我们使用 NVMe-over-Fiber,这是 AIO 的最佳用例,因此我们对此感到特别兴奋。

虽然 AIO 框架已经引入,但尚未得到广泛应用,因此目前需要对预期进行管理。Tomas Vondra 的这篇博文包含了我见过的最好的性能概述。

Source for the image is a blog post by Tomas Vondra

图片来源:Tomas Vondra 的一篇博客文章

简而言之:

  • Sequential scans 显示 2 倍或 3 倍的改善!
  • Index scan 没有显示出任何改进。
  • Bitmap scan 显示了一些改进,但在与低 io_workers 一起使用时性能也出现了倒退

此外,AIO 应该减少了后台维护任务的开销。

OAuth 2.0

OAuth 支持意味着现在有了一个良好且标准的方法来避免那些共享的 长期密码。您可以使用 SSO 提供程序以交互方式访问 Postgres,并且应用程序可以使用 OAuth 工作流向 Postgres 进行身份验证。

实际上,对于交互式登录,Postgres 连接字符串可能看起来像这样:

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

应用程序可以通过连接字符串使用token,如下所示:

shell 复制代码
host=example.com oauth_issuer=https://... oauth_client_id=... oauth_token=...

我们计划在不久的将来将其应用于 Xata,作为静态密码的替代方案

Improvements for developers 面向开发人员的改进

如果您从开发人员的角度使用 PostgreSQL,那么这可能是您最感兴趣的部分。

UUID v7

如何生成主键 (PK) 一直是开发者们热议的话题,现在有了一个简单的解决方案。在 Postgres 18 之前,您可以使用原生的 UUID 类型 UUID v4,但这会降低性能,因为 UUIDv4 不是单调的,这意味着它们会影响索引局部性,而且压缩效果不佳。UUIDv7 解决了这些问题,它支持排序,并在开头嵌入了当前时间。

由于 UUIDv7 规范在实施上允许一定的自由,因此以下是从提交消息中获取的 Postgres 特定细节:

在我们的实现中,12 位亚毫秒时间戳小数部分紧跟在时间戳之后,存储在 RFC 中称为"rand_a"的空间中。这确保了毫秒级的额外单调性。rand_a 位也充当计数器。我们选择亚毫秒时间戳,以便即使系统时钟倒退或以非常高的频率生成 UUID,它对于同一后端内生成的 UUID 也单调递增。因此,在同一后端内生成的 UUID 的单调性得到了保证。

简单用法如下:

sql 复制代码
select uuidv7();

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

该函数允许传入一个 interval 类型的参数,您可以使用该参数获取带有过去或未来时间戳的 UUIDv7。例如,以下代码将返回一个带有昨天时间戳的 UUIDv7:

sql 复制代码
select uuidv7(INTERVAL '-1 day');

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

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

sql 复制代码
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 示例:

sql 复制代码
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

Virtual Generated columns, computed on read 虚拟生成列,在读取时计算

Postgres 18 增加了一种新的生成列变体: VIRTUAL 。它们像视图一样在读取时计算,而不像存储的生成列那样,它们像物化视图一样在写入时计算。

以下是一个简单的例子:

sql 复制代码
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 设为默认设置的动机在提交信息中:

为了与其他各种 SQL 产品匹配,默认类型是 VIRTUAL,而不是 STORED。(SQL 标准对此没有做出任何规范,但它也不知道 VIRTUAL 或 STORED 是什么。)(此外,默认类型是虚拟视图,而不是物化视图。)

提交消息中还要注意的另一件事:它们是在读取时计算的,但在存储方面它们并不是完全零成本

虚拟生成列在元组中以空值的形式存储。(此补丁的早期版本曾试图完全不存储它们。但是,如果元组中某个列完全缺失,很多东西就会崩溃或混乱。这是一种折衷,而且比强制使用存储生成列更节省空间。如果我们找到改进方法,pg_upgrade 的一点小技巧或许可以让我们升级到更新的方案。)

Generated columns included in logical replication 逻辑复制中包含的生成列

说到生成的列,Postgres 18 增加了将其包含在逻辑复制流中的功能。之前,我们假设跟随副本可以再次生成这些列,但如今逻辑复制的应用范围远不止 Postgres 到 Postgres。

对于 pgstream 和 Debezium 等 CDC 工具来说,这是一个好消息,因为它们现在可以获取生成的列。

Temporal DB improvements Temporal DB改进

首先,让我们快速定义一下时态数据库(temporal databases): 它们是跟踪随时间变化的数据的 数据库。时态数据库不仅存储最新的信息状态 ,还记录数据在其生命周期内的历史状态,而不是仅仅存储最新的信息状态。

Postgres 18 改进了时态数据库用例,支持主键和唯一约束WITHOUT OVERLAPS 子句。这通过强制执行键时态规则( 对于同一业务键,有效时间段不得重叠)使 Postgres 更接近时态数据库的功能。

我们正在撰写一篇关于使用 PostgreSQL 作为时间数据库的深入博客文章,但现在是一个简单的例子:

sql 复制代码
-- 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)
);

上述 PK 保证同一房间在任何时间点都不会被超额预订。

此外,外键约束现在可以通过 PERIOD 关键字引用周期。此功能支持范围和多范围类型。时间外键检查范围包含性,而不是相等性。

Create NOT NULL constraint with NOT VALID 使用 NOT VALID 创建 NOT NULL 约束

此更改将 NOT NULL 约束添加到可添加为 NOT VALID 约束列表中。这是什么意思?假设您有一列当前包含 NULL 值。您不能简单地添加 NOT NULL 约束,因为它无效(而且它会在 Postgres 验证所有值时锁定表)。如果您先回填数据以删除 NULL 值,则新插入的数据可能会添加更多 NULL 值。

相反, NOT VALID 允许以下操作:

  • 将约束添加为 NOT VALID 。这是一个快速的操作,因为它不检查现有行。但从现在开始,插入操作必须为给定列指定非空值。
  • 回填数据以删除所有 NULL。
  • 验证约束。此操作无需锁定表的读写操作(技术上来说,仍然有锁,但不会阻止读写操作)。

以下是一个示例会话:

sql 复制代码
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;

小插件: pgroll 是我们的一个开源项目,它可以帮助您进行各种模式更改而无需锁定,并使模式更改易于逆转。

分区表上的 FK 约束无效

模式变更操作的另一个好处与上述类似,分区表上的外键约束可以声明为 NOT VALID 。例如,如果 events 是一个分区表,其中的数据引用了 accounts 表,则以下操作有效:

sql 复制代码
ALTER TABLE events
  ADD CONSTRAINT events_account_fk
  FOREIGN KEY (account_id) REFERENCES accounts(id)
  NOT VALID;

并且可以逐个分区地检查验证,如果您想进一步最小化锁定,这很方便。

New protocol version 新协议版本

Postgres 18 自 2003 年以来首次增加了线路协议的版本号!这也是次版本号首次增加。新版本号为 3.2,其描述中包含了版本升级的原因:

查询取消时使用的密钥从 4 个字节扩展为可变长度字段。BackendKeyData 消息也进行了相应修改,CancelRequest 消息也被重新定义为可变长度有效负载。

如果您好奇为什么它是 3.2 版本而不是 3.1 版本,答案就在上面链接的同一页面上:

保留。任何 PostgreSQL 版本都未使用 3.1 版本,但由于流行的 pgbouncer 应用程序的旧版本在协议协商中存在错误,导致其错误地声称支持 3.1 版本,因此将其跳过。

libpq 客户端库目前仍默认使用 3.0 版本,直到上层(例如驱动程序、池化器、代理)添加对新协议版本的支持。这一点,加上重大变更较小,意味着我们不应该遇到由新版本引起的兼容性问题。

Improvements for operations

Faster major version upgrades 更快的主要版本升级

主版本升级是 Xata 一直以来努力的另一个主题。Postgres 18 在这方面带来了多项有希望的改进。

首先, pg_upgrade 通常应该更快,尤其是在同一个集群上拥有多个数据库,或者更普遍地说,拥有大量对象(表、视图、序列等)的情况下。这是因为它现在有了一个可以并行执行多个"作业"的框架,并且能够更智能地避免不必要的工作和 fsync。

其次, pg_upgrade 现在还会迁移上一版本的统计信息,这意味着升级后规划器将拥有其所需的关键信息,从而以最佳方式完成工作。这降低了升级后性能下降的风险。

What I find particularly cool about this, is that the statistic transfer is actually done by pg_dump, which now gets the --no-statistics and --statistics-only options. So you can use this in other situations as well, for example, for Blue-Green deployments via logical replication.

我觉得特别酷的是,统计信息的传输实际上是由 pg_dump 完成的,它现在拥有 --no-statistics--statistics-only 选项。因此,您也可以在其他情况下使用它,例如,通过逻辑复制进行蓝绿部署。

Easier extension management in K8s environments

在 K8s 环境中更轻松地管理扩展

新增了 extension_control_path 配置项,允许控制 Postgres 查找扩展的位置。此项新增功能由 CloudNativePG 项目提出 ,最终目标是让 Kubernetes 运维人员能够更轻松/更轻松地进行声明式扩展管理。

在此之前,由于镜像需要不可变,唯一真正可行的解决方案是使用所需的扩展子集构建自定义镜像。现在,您可以使用最小镜像,并使用其他扩展挂载镜像卷。

这是我们将在 Xata 上利用的另一个功能,最终将在添加新扩展时带来更好的安全性和更大的灵活性。

VACUUM improvements VACUUM 改进

Postgres 18 带来了几项与真空相关的改进。

可以降低激进清理的成本。当 Postgres 意识到存在事务回绕的风险时,就需要激进清理,这样它就知道需要更快地冻结旧元组(行)。

为了分摊激进清理的开销,Postgres 18 在常规清理过程中会积极扫描一些所有可见但并非全部冻结的页面。这意味着在常规清理过程中工作量会增加,但可以更好地避免最坏情况的发生。

Postgres 18 还改变了插入阈值的计算,使其不包含冻结页面,这意味着在插入大量表时它通常会更频繁。

清理的可观察性也得到了提升,因为现在有一个名为 track_cost_delay_timing 的新设置,用于收集基于成本的清理延迟的时间统计信息。请注意,此参数默认处于关闭状态,因为它会反复查询操作系统获取当前时间,这在某些平台上可能会造成很大的开销。幸运的是,Postgres 附带了一个方便的工具 pg_test_timing 因此您可以了解在您的架构上启用此功能是否可行。

Improvements for observability / monitoring

可观察性/监控的改进

EXPLAIN improvements EXPLAIN 改进

Postgres 18 对 EXPLAIN 语句进行了几项小改进。

值得注意的是,运行 EXPLAIN ANALYZE 时, BUFFERS 现在是默认值。 提交信息解释了(双关语)更改默认值的理由:

过去几年里,关于将 EXPLAIN 的 BUFFERS 选项与 ANALYZE 选项同时启用的话题已经出现过几次。从很多方面来看,这样做似乎是一个好主意,因为它可以让用户更清楚地了解为什么某个查询的运行速度比他们预期的要慢。此外,根据我(David)的个人经验,我见过用户在邮件列表中发帖,提出两个相同的计划,一个慢,一个快,询问为什么他们的查询有时会很慢。很多情况下,这是由于额外的读取操作造成的。默认启用 BUFFERS 可能有助于减少其中一些疑问,如果不能,可以在用户发帖之前让他们更清楚地了解原因,或者在额外的 I/O 操作导致速度缓慢时,避免用户往返邮件列表。

除上述内容外, EXPLAIN 还包含更多改进的信息:Material 节点的内存/磁盘使用情况、索引搜索计数、禁用节点的数量等。

More statements in pg_stat_statements

pg_stat_statements 中的更多语句

另一项有助于提高可观察性的改进: CREATE TABLE ASDECLARE CURSOR 等语句现在会为其创建的内部查询分配查询 ID。这样做的好处是,这些查询现在将显示在 pg_stat_statements 等文件中,因为查询 ID 是必需的。

Logging of lock acquisition failures

锁获取失败的日志

此更改引入了一个新的配置参数: log_lock_failure 。如果启用(默认关闭),则在获取锁失败时会生成详细的日志消息。目前,它仅支持记录由 SELECT ... NOWAIT 导致的锁失败。

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

Per process statistics 每个进程的统计信息

此更改改进了统计基础架构,使其能够在进程生命周期内保存每个进程的统计信息。提交消息解释了其工作原理:

这会在 pgstats 中添加一种新的变量编号统计信息类型,其中统计信息条目的对象 ID 键基于后端的进程号。这作为可同时存在的统计信息条目数量的上限。这些条目在后端身份验证成功后启动时创建,并在后端退出时被删除,因此只要后端启动并运行,统计信息条目就会一直存在。

这个新基础架构的第一个用户是一个新函数: pg_stat_get_backend_io() ,它可以收集特定后端/进程的 IO 统计信息。示例用法:

sql 复制代码
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    | 

Tracking connection establishment durations

跟踪连接建立持续时间

Postgres 18 添加了记录建立连接和设置后端所花费的时间的选项,直到连接准备好执行其第一个查询为止。日志消息包含三个持续时间:

  • 总设置持续时间(total setup duration)(从postmaster 接受传入连接开始,到连接准备好查询时结束)
  • 分叉新后端所花费的时间( fork the new backend )
  • 验证用户身份所花费的时间

要启用此功能,您需要将 setup_durations 添加到 log_connections 配置参数中。

Performance improvements and optimizations

性能改进和优化

Index optimization: B-tree skip scan

索引优化:B-tree skip scan

假设你有一个类似 (col1, col2, col3) 的多列索引。在 Postgres 18 之前,只有在条件中指定最左边的列时,这样的索引才能被有效使用。因此,所有这些都将使用索引:

sql 复制代码
SELECT * FROM foo WHERE col1 = '...';
SELECT * FROM foo WHERE col1 = '...' AND col2 = '...';
SELECT * FROM foo WHERE col1 = '...' AND col2 = '...' AND col3 = '...';

但这些通常不会使用索引:

sql 复制代码
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(<every possible 'a' value> ) AND b = 5" 扫描键。也就是说,预处理为省略的前缀列"a"生成一个"跳过数组"(和一个输出扫描键),这使得在继续扫描时可以安全地将扫描键标记为"b"。因此,扫描能够通过应用"a"和"b"键反复重新定位自身。[...] 测试表明,对具有低基数跳过前缀列的索引进行跳过扫描可以比等效的完整索引扫描(或顺序扫描)快几个数量级。通常,扫描跳过列的基数限制了可以跳过的叶子页的数量。

SQL-language functions use the plan cache

SQL 语言函数使用计划缓存

这有助于更好地内联 SQL 函数中的查询。以下是提交消息:

在 SQL 函数(如果它们没有内联)的历史实现中,我们会在外部查询的首次调用时为所有包含的查询构建计划,然后在外部查询的持续时间内重复使用这些计划,然后就忘记了一切。这并不理想,尤其是因为这些计划无法根据函数参数的特定值进行自定义。

Self-Join Elimination 自连接消除

如果证明可以用扫描替换连接而不影响查询结果,则自连接消除 (SJE) 功能会在查询树中删除普通表与其自身的内部连接。

这种优化减少了某种形式的冗余,本质上可以提高规划器的估算精度,并减少后续层级的工作量。分区表尤其受益于此,因为它可以更早地识别出需要进行分区修剪的可能性。

Detect redundant GROUP BY columns using UNIQUE indexes

使用 UNIQUE 索引检测冗余的 GROUP BY 列

规划器优化适用于使用多列 UNIQUE 索引 GROUP BY 情况。在这种情况下,Postgres 可以只使用一列,因为 UNIQUE 索引可以确保分组是等价的。

以下是受益于此优化的示例:

sql 复制代码
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 已经针对主键实现了此优化,现在将此优化扩展至任何多列 UNIQUE 键。请注意,UNIQUE 索引中的列需要标记为 NOT NULL ,或者索引必须使用 NULLS NOT DISTINCT

Reordering DISTINCT values to reduce sorting

重新排序 DISTINCT 值以减少排序

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

sql 复制代码
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

Convert 'x IN (VALUES ...)' to 'x = ANY ...' when possible

尽可能将"x IN (VALUES ...)"转换为"x = ANY ..."

这种优化的目的是简化查询树,消除不必要的连接。以下是示例情况:

sql 复制代码
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 语句如下:

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

提交消息解释了为什么这样更快:

由于 VALUES 描述的是一个关系表,而此类列表的值是表中的一行,因此优化器可能会因为无法通过 MCV 统计信息估计基数而面临低估问题。基数评估机制可以与数组包含检查操作配合使用。如果数组足够小(< 100 个元素),它将逐个元素执行统计评估。

Case folding

Postgres 18 添加了新的 casefold() 函数,该函数与 lower() 类似,但避免了不区分大小写的匹配中的边缘情况问题。对于支持该函数的排序规则, casefold() 可以处理包含两种以上大小写变体或多个字符大小写变体的字符。

以下是一些边缘情况的例子(取自邮件列表 ),其中大小写折叠比降低效果更好:

  • 某些字符具有两种以上的大小写形式,例如"Σ"(U+03A3),其小写形式可以是"σ"(U+03C3)或"ς"(U+03C2)。casefold casefold() 函数会将该字符的所有大小写形式转换为"σ"。
  • 字符"İ"(U+0130,大写字母 I 带点)小写为"i",这在没有预料到这种情况的语言环境中可能会出现问题。
  • 如果 Unicode 中添加了新的小写字符, lower() 的结果可能会发生变化。

Faster lower(), upper() 更快的 lower()、upper()

与上述相关,Postgres 18 对 lower()upper() 的实现也更快了。优化在于映射表的生成方式,并具有以下优势(摘自邮件列表 ):

  • 删除了所有表内的 Unicode 代码点(无符号整数)的存储。
  • 将主表的记录从 3003 条减少到 1575 条(删除重复项)。
  • 在主表中用 uin8_t 替换指针(本质上是 uint64_t)。
  • 减少了在表中查找记录的时间。
  • 减小最终目标文件的大小。

提交信息包括考虑的其他方法的说明:

我们还考虑了其​​他方法,例如将这些范围表示为其他结构(而不​​是生成函数中的分支),或者采用其他方法,例如基数树或完美哈希。作者实现并测试了这些替代方案,最终确定了生成的分支。

Faster GiST index building for ranges

更快地为范围构建 GiST 索引

GiST 支持"排序构建"模式:如果输入元组已经排序,它可以更快地构建树,并且打包效果更好。但为了高效地对范围进行排序,规划器/执行器需要一个特殊的 sortsupport 函数 。该函数已在本次提交中添加。

Array convenience functions

数组便捷函数

Postgres 18 附带两个用于数组的便捷函数array_reverse()array_sort()

每个示例的简单示例:

sql 复制代码
SELECT array_reverse(ARRAY[1,2,3]);

 array_reverse 
---------------
 {3,2,1}
(1 row)
sql 复制代码
 SELECT array_sort(ARRAY[3,1,2]);

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

json_strip_nulls() removes null array elements

json_strip_nulls() 删除空数组元素

JSON 函数 json_strip_nulls() 新增了一个参数: strip_in_arrays 。默认值为 false。如果为 true,则删除空值数组元素以及空值对象字段。仅包含单个空值的 JSON 不受影响。

Add function to get the ACL for a database object

添加函数以获取数据库对象的 ACL

Postgres 18 引入了一个新函数 pg_get_acl() ,它对于检索和检查与数据库对象关联的权限非常有用。以下是一个例子:

sql 复制代码
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 角色获得写入访问权限。

相关推荐
渣哥4 小时前
为什么几乎所有 Java 项目都离不开 IoC?Spring 控制反转的优势惊人!
javascript·后端·面试
用户3856803349964 小时前
appium从入门到精通php,移动端自动化测试Appium 从入门到项目实战Python版
后端
天天摸鱼的java工程师4 小时前
SpringCloud + Nacos + Feign + Resilience4j:微服务间调用的熔断降级与重试策略
后端
长安城没有风5 小时前
从入门到精通【Redis】初识Redis哨兵机制(Sentinel)
java·数据库·redis·后端
canonical_entropy5 小时前
范式重构:可逆计算如何颠覆DDD的经典模式
后端·低代码·领域驱动设计
绝无仅有5 小时前
某大厂跳动Java面试真题之问题与解答总结(五)
后端·面试·github
我是天龙_绍5 小时前
SpringBoot如何整合Mybatis-Plus
后端
绝无仅有5 小时前
某大厂跳动Java面试真题之问题与解答总结(四)
后端·面试·github
昵称为空C5 小时前
Jmeter 性能测试利器-1(入门指南)
后端·测试