Postgres 18 中值得开发者关注的新功能
PostgreSQL 18 在 2025 年 9 月 25 日正式发布。毫无疑问,最具影响力的变更必定是全新的异步 I/O(AIO)子系统。不过,本文会聚焦那些影响开发者日常工作的功能,首先介绍的是原生 UUID v7 支持。
原生支持 UUID v7
uuidv7()函数,用来生成 UUID v7。几乎终结了关于主键应采用 SERIAL/IDENTITY 类型还是 UUID 的长期争论。UUID v7 兼具两者的优势:既保留了 UUID 的全局唯一性,又具备使序列键性能优异的顺序特性。uuid-ossp并不支持 v7,而那些支持的扩展在很多发行版或云服务里并不默认安装。这导致不少团队只能在应用层自己实现 UUID v7 的生成逻辑。现在数据库原生支持后,使用成本大大降低。-- 生成 UUIDv7SELECT uuidv7();-- 用 UUID v7 作为主键建表CREATE TABLE orders (id UUID PRIMARY KEY DEFAULT uuidv7(),customer_id INT,total DECIMAL(10,2),created_at TIMESTAMP DEFAULT NOW());
VIRTUAL 生成列
PostgreSQL 在 12 版本时就支持了生成列,但当时只能用STORED,也就是写入时计算并保存到磁盘。这种方式不仅占空间,新增 STORED 列时还可能重写整张表。
PostgreSQL 18 引入了 VIRTUAL 选项,并将其设为生成列的默认行为。此类列在读取时而非写入时计算其值。这种更常见的做法使其成为默认设置,使 PostgreSQL 与其他主流数据库保持一致。
-- 用 virtual生成列建表CREATE TABLE employees (id SERIAL PRIMARY KEY,first_name TEXT NOT NULL,last_name TEXT NOT NULL,salary DECIMAL(10,2),-- 由于 VIRTUAL 是默认选项,您也可以跳过该选项annual_salary DECIMAL(12,2) GENERATED ALWAYS AS (salary * 12) VIRTUAL);-- 插入数据(生成的列将自动计算)INSERT INTO employees (first_name, last_name, salary) VALUES('John', 'Doe', 5000.00),('Jane', 'Smith', 6500.00);SELECT first_name, last_name, salary, annual_salaryFROM employees;
这样做的好处:
节省存储空间:对于无需物理存储的衍生数据,虚拟列可消除冗余磁盘占用。
动态计算:某些计算在查询时执行更具优势,尤其当其依赖当前系统状态时。
RETURNING 子句支持 OLD/NEW 值
PostgreSQL 18 扩展了RETURNING子句,现在可以同时返回修改前(OLD)和修改后(NEW)的行值。这样写审计日志就简单多了。
UPDATE 操作
-- 新价格并同时查看新旧数值UPDATE productsSET price = price * 1.10WHERE price <= 99.99RETURNINGname,old.price AS old_price,new.price AS new_price,new.price - old.price AS price_change;
带有 ON CONFLICT 的插入-- 带变更跟踪的插入或更新操作INSERT INTO products (name, price) VALUES ('Widget', 25.00)ON CONFLICT (name) DO UPDATE SET price = EXCLUDED.priceRETURNINGname,old.price AS previous_price,new.price AS current_price,(old.price IS NULL) AS is_new_record;
删除操作-- 追踪已删除内容DELETE FROM productsWHERE price < 10.00RETURNINGold.name AS deleted_product,old.price AS deleted_price;
EXPLAIN ANALYZE 默认显示 BUFFERS
postgres=# EXPLAIN ANALYZE SELECT*FROM users WHERE age >25;-- PostgreSQL 18:自动包含 BUFFERSSeq Scan on users (cost=0.00..18.75rows=125 width=64) (actual time=0.029..0.087rows=178 loops=1)Filter: (age >25)Rows Removed byFilter: 89Buffers: shared hit=12Planning:Buffers: shared hit=156 read=3I/O Timings: shared read=0.024Planning Time: 0.512 msExecution Time: 0.734 ms
pg_get_acl():权限检查更方便
pg_get_acl()函数,用于统一查看数据库对象的权限(ACL)。若您曾耗费大量时间调试臭名昭著的 ERROR 42501: 权限被拒绝错误信息,便会深切体会到统一检查对象权限方式的价值。过去如果遇到permission denied错误,不同对象类型要查不同的系统表( pg_class、pg_proc、pg_namespace 等),非常麻烦。现在一个函数就能搞定。
postgres=# SELECT(pg_identify_object(s.classid,s.objid,s.objsubid)).*,pg_catalog.pg_get_acl(s.classid,s.objid,s.objsubid) AS aclFROM pg_catalog.pg_shdepend AS sJOIN pg_catalog.pg_database AS dON d.datname = current_database() ANDd.oid = s.dbidJOIN pg_catalog.pg_authid AS aON a.oid = s.refobjid ANDs.refclassid = 'pg_authid'::regclassWHERE s.deptype = 'a';-[ RECORD 1 ]-----------------------------------------type | tableschema | publicname | testtabidentity | public.testtabacl | {postgres=arwdDxtm/postgres,foo=r/postgres}
这对开发者调试权限问题很有帮助,对自动化系统或 AI agent 来说也更加友好。
延展阅读
DBA 视角:Postgres 18 新版本,带来了哪些变化?