当前位置: 首页 > news >正文

[译] 我最爱的PostgreSQL 18特性:虚拟生成列

原文:https://tselai.com/virtual-gencolumns

在PostgreSQL 18的新特性中,异步I/O、UUID v7以及升级后统计功能或许会成为众人瞩目的焦点。但对我而言,即将发布的这个版本里,最让我青睐的特性当属虚拟生成列(相关文档可参考PostgreSQL 18官方文档-生成列)。

生成列这类特性,能帮你省去大量重复代码的编写工作,让操作更轻松。无需在视图、查询或触发器中反复写相同的表达式,数据库会帮你统一管理。它将数据处理逻辑与数据本身紧密结合,减少重复操作,也让数据库模式(schema)的逻辑更清晰易懂。

生成列主要分为两种类型:存储型(stored)虚拟型(virtual)

  • 存储型生成列:在数据插入或更新时计算一次结果,并将结果持久化存储在磁盘上(会占用存储空间)。像普通列一样,它也支持创建索引。对于计算成本高、不想每次查询都重新计算的表达式,存储型生成列是理想选择。

  • 虚拟型生成列(PostgreSQL 18新增特性):不会将结果存储在磁盘上,仅在执行查询时实时计算。对于计算量小的表达式,或是不想增加表体积的场景,虚拟型生成列尤为适用。

来看下面这个示例:

create table users (id serial primary key,name text not null,-- 存储型生成列:结果持久化到磁盘,支持索引name_upper textgenerated always as (upper(name)) stored,-- 虚拟型生成列:仅在查询时计算结果name_lower textgenerated always as (lower(name)) virtual
);insert into users (name) values ('Florents'), ('Αθηνά'), ('postgres');
select id, name, name_upper, name_lower from users;id |   name   | name_upper | name_lower 
----+----------+------------+------------1 | Florents | FLORENTS   | florents2 | Αθηνά    | ΑΘΗΝΆ      | αθηνά3 | postgres | POSTGRES   | postgres

在这个例子中,name_upper是存储型列,其结果会写入磁盘;而name_lower是虚拟型列,结果仅在查询时实时计算。

生成列的有趣之处在于,它借鉴了响应式编程的思路——正是这种思路让Microsoft Excel成为史上最受欢迎、最高效且生命力极强的软件之一。你只需定义一个源列(自变量),其他生成列(因变量)就能通过源列的函数关系自动生成。

总的来说,我发现生成列是个非常实用的工具:它能减少数据库维护所需的精力,让你可以将更多心思投入到合理的数据库设计与规范化工作中。

实际应用案例:全文搜索

假设你需要支持多语言或多文本配置的搜索功能,这时可以创建三个不同的生成列,每个列对应一种文本搜索配置:

create table docs (id serial primary key,body text not null,body_fts_simple tsvectorgenerated always as (to_tsvector('simple', body)) stored,body_fts_en tsvectorgenerated always as (to_tsvector('english', body)) stored,body_fts_el tsvectorgenerated always as (to_tsvector('greek', body)) stored
);

接下来,你可以为每个tsvector列创建索引,再根据用户使用的语言执行查询:

create index on docs using gin (body_fts_simple);
create index on docs using gin (body_fts_en);
create index on docs using gin (body_fts_el);

这种方式无需使用触发器,能让数据库模式保持声明式风格,同时确保全文搜索(FTS)列始终与基础文本内容保持一致。

当然,你也可以不存储这些生成列,直接基于表达式创建索引。但实际操作中我发现,调试搜索结果和预处理步骤时,往往需要查看具体的词汇单元(token)和评分,此时存储型生成列的优势就显现出来了。

当源列后续发生更新时,生成列会重新计算结果,并根据预设的函数表达式更新自身的值。

虽然你也可以用触发器实现相同的逻辑,但随着规模扩大,要维护数据库的完整性并保证操作不出错,难度会非常大。

生成列还能让你像搭积木一样,逐个添加列来构建数据表。在实际应用中,我见过很多人用生成列“扁平化”PostgreSQL中的JSON文档——这是一种非常流行的用法。需要说明的是,存储型生成列从PostgreSQL 12版本就已存在,此后PostgreSQL的JSON功能不断升级:从更完善的jsonpath表达式,到新增的JSON_TABLE函数等。

在这些JSON功能尚未出现,或是有人对复杂的jsonpath不熟悉、更倾向于使用jsonb -> 'k1' -> 'k2'这类简单操作时,生成列曾是非常便捷的替代方案。

不过,使用存储型生成列虽方便,却要付出额外的存储成本——毕竟JSON文档通常包含大量文本。如果某些JSON字段只在SELECT语句中调用,那么为其占用存储空间就显得得不偿失了。而虚拟型生成列则能解决这个问题:它可以将JSON字段以普通列的形式呈现,同时不会在磁盘上重复存储数据。这样既能让查询语句更简洁,又能实现零存储开销。

若想了解虚拟生成列的内部工作原理,可参考Peter Eisentraut发起的原始补丁讨论线程:https://www.postgresql.org/message-id/flat/a368248e-69e4-40be-9c07-6c3b5880b0a6%40eisentraut.org。

关键权衡与注意事项

  1. 性能权衡
    存储型列会增加写入操作的开销——每次插入或更新数据时,都需要重新计算并存储生成列的值。但它的优势在于读取速度更快,且能直接利用索引。虚拟型列则相反:它让写入操作更轻便,但会将计算压力转移到查询阶段。简单来说,存储型列是“预计算存储”,虚拟型列是“按需计算”。

  2. ** schema演进 **
    给大型表添加虚拟列可以瞬间完成,因为无需向磁盘写入任何数据;而添加存储型列则需要回填数据,甚至可能触发全表重写。因此,当你需要快速实验新功能,又不想影响生产环境中的表时,虚拟列会是更合适的选择。

  3. 功能限制
    生成列存在一些明显的限制,这些限制的目的是避免数据库逻辑变得混乱。所有限制可在PostgreSQL官方文档中查看。

  4. 安全隐患
    虚拟列的实时计算特性还存在一些容易被忽视的安全问题,相关讨论可参考此线程:https://www.postgresql.org/message-id/flat/CAK_s-G2Q7de8Q0qOYUR%3D_CTB5FzzVBm5iZjOp%2BmeVWpMpmfO0w%40mail.gmail.com。如果你的数据库大量依赖自定义函数或用户定义类型,建议仔细阅读该讨论。虚拟列可能会出现一些意外行为,若规划不当,引入虚拟列甚至可能导致严重问题。

http://www.wxhsa.cn/company.asp?id=1714

相关文章:

  • nasm 的 Hello, world 在 Windows 10 x64 上
  • 实用指南:52.前端的后端模式:为每个客户端定制专属「管家服务」
  • Agilent 34401A台式万用表远程读表
  • Java 在大数据处理与人工智能中的应用
  • 马克思,本就是一位独立研究者
  • 产品二期,从GPT5规划开始
  • Redis能抗住百万并发的秘密
  • 接受 “未完成态”,是一种能力
  • 深入理解JNI、安全点与循环优化:构建高健壮性Java应用
  • 英语_阅读_fascinating facts about water_待读
  • AI自动化测试全攻略:从AI 自动化测试实战到AI 智能测试平台开发!
  • LG9691
  • 即时通讯小程序 - 实践
  • PHP serialize 序列化完全指南
  • CF2112D
  • CF2112C
  • CF342C
  • ICPC/XCPC 做题记录
  • LG9648
  • LG5689
  • 近五年 CSP NOIP 补题记录
  • CF2111C
  • 唐人日记
  • CF2111B
  • ABC394F
  • LG11793
  • ABC394G
  • MX 炼石 2026 NOIP #5
  • 0126_状态模式(State)
  • Visual Studio 2026 预览体验版现已发布,一起来看看带来哪些新功能!