PostgreSQL 18 已于 09-25 号正式发布,pg 社区用户称是多年来最大的一次更新,本文梳理了社区用户广泛讨论的一些新特性,让我们一起来围观一下,看看是怎么个大更新!
友情提示:博主日常是使用 mysql 为主,下文会情不自禁的横向对比, 总结不到位的地方勿喷!
据说 PostgreSQL 团队做这个优化折腾了五年多,这次发誓要 pg 在读数据方式彻底 "提速换挡"!(MySQL用户嘴角微微上扬:“冷知识!MySQL Innodb 存储引擎很早就使用了 AIO,折腾五年我以为是什么黑科技呢!”)
在 pg 18 之前,单个后台进程发起磁盘 I/O 请求时,采用的是阻塞式同步 I/O(Synchronous I/O),这意味着该进程必须等待 I/O 完成才能继续执行。而 18 版本在底层做了一个重大升级 —— 引入了全新的 异步 I/O(AIO)子系统。官方解释是:这个特性可以提升 I/O 吞吐量,同时降低 I/O 延迟。简单来说,就是让数据库在读文件时,更快、更流畅。 (官方宣称读性能在某些场景下能提升 3+ 倍)
我们可以在 pg 的配置文件中,通过 iomethod 参数来启用异步 I/O 模式。对于 Linux 系统,建议使用 iouring;而其他系统也有基于 worker 的通用实现。
pg 18 官网文档中定义的 io_method 可选的配置:
|
|
|
normal |
|
aio |
|
io_uring |
仅限 Linux,基于 io_uring 的高性能实现
|
worker |
|
目前的版本主要针对文件系统的读取操作,比如顺序扫描、 vacuum 操作。另外,字典表还新增了一个视图 pg_aios,可以用来查看 AIO 使用到的文件句柄。
pg 18 引入了 uuidv7() 函数,用于生成 UUID v7 字符串。 这意味着长久以来关于主键到底该用 SERIAL / IDENTITY 还是 UUID 的争论,有了一个官方支持的解决方案!
UUIDv7 把两者的优点结合了起来:既保留了 UUID 的全局唯一性,又具备顺序性,从而在性能上更接近传统的自增主键。(MySQL 用户表示也想要这个函数,但没有也无所谓,后端代码自己生成写入表里也是可以的)
-- uuid v7 函数:
SELECT uuidv7();
uuidv7
--------------------------------------
01987fb8-b258-70fd-a574-1c3f9b89ee21
-- 建表的时候 使用 uuid v7 当主键
createtable t1 (
iduuiddefault uuidv7() primary key,
age INT,
...
...
);
在此之前,虽然 PostgreSQL 的一些扩展可以生成 UUIDv7,但这些扩展通常不会随 PostgreSQL 默认安装包提供, 如果开发者想使用 UUIDv7,只能在应用层自己实现生成逻辑。而 PostgreSQL 18 版本原生提供了支持,为开发者解决了这个额外的工作负担!
虚拟生成列(Virtual Generated Column)
生成列并不是什么新技术,其他数据库比如 Oracle、MySQL、TiDB 中都有支持。pg 之前的版本中也支持,只是之前的版本它要存储数据,支持 virtual 模式后可以不存了。
简而言之,生成列是一种特殊的字段,他的值是表达式动态计算而来的,其值依赖于表中的其他列,而不是由用户显式插入的。它的作用类似于“列级别的视图(View)
-
STORED:在写入(INSERT/UPDATE)时计算并存储,其行为类似物化视图。
-
VIRTUAL:不占用存储空间,读取时才动态计算,其行为类似普通视图。
从 PostgreSQL 18 开始,生成列默认采用 VIRTUAL 模式,当然你也可以显示声明是使用 VIRTUAL 还是 STORED。
# 虚拟生成列使用示例
CREATETABLE people (
height_cm numeric,
height_in numericGENERATEDALWAYSAS (height_cm / 2.54) -- 默认 VIRTUAL
);
VIRTUAL 模式不占用额外磁盘空间,添加 VIRTUAL 生成列时也无需重写整张表,相比之下,STORED 模式会在写入时计算并持久化生成列的值,虽然查询速度更快,但在修改表结构时会带来额外的 I/O 开销。(对 pg 过往的版本来说,这确实算一个不错的更新,不过 mysql 在 5.7 版本就已经支持了)
这个功能对不太了解数据库的同学可能有点陌生,我举个例子:如果你需要用其他字段拼接姓名、计算金额得出新的数据列要使用,又想避免大表因为加列引起重建表带来的性能开销,此时可以考虑使用生成列。(博主职业生涯中还没有用过,这个场景大部分时候可以交给后端处理。但必须承认,某些场景下确实好用!)
pg 18 版本这个功能有点意思。 比如在日常工作中,我们有时希望在执行 INSERT、UPDATE、DELETE 或 MERGE 操作时,能够直接拿到被更新后的数据。如果没有 returning 子句,就需要立即再执行一条 SELECT 操作获取刚刚变更的数据,这要执行两次 SQL,还可能因为条件判断、并发变化导致结果不稳定。
returning 子句允许你在执行写操作的同时,直接获取变更后的那一行(或多行)的数据。这样你就省掉了额外的 SELECT 查询。
INSERTINTOusers(firstname, lastname)
VALUES ('Joe', 'Cool') RETURNING *;
# 效果相当于再执行一次 select * from xx
# 但只会返回刚刚你插入的数据
INSERTINTO stu(firstname, lastname)
VALUES ('Siri', 'Lot') RETURNINGid,firstname;
# 仅返回需要的字段数据
# 效果相当于再执行一次 select * from
# 但只会返回刚刚你更新后的数据
UPDATE products
SET price = price * 1.10
WHERE price <= 99.99
RETURNINGname, price AS new_price;
# 这样会返回被删除前的每一行记录的完整数据
DELETEFROMusers
WHEREstatus = 'inactive'
RETURNING *;
"pg 18 这个功能说实话是有点骚!这应该是跟 Oracle 学的‘高级货’。MySQL 用户嘛……(说不羡慕那是假的,但用多写一次 SQL 也能凑合,又不是不能用!”)
explain 默认显示缓冲区(BUFFERS)信息,不用额外加参数;还增加了 WAL(Write-Ahead Logging)、CPU 以及平均读取 page 的统计。
这个功能方便 DBA 分析 SQL 性能,但对大多数开发人来说,影响不大。
如果你有一个联合索引 (A, B, C),查询条件只用到后面的列(不符合最左前缀),比如 WHERE B = 100,优化器往往无法充分利用索引,只能扫描全表,性能较低。
有了这个功能,即使你的查询条件没有包含联合索引的第一个列(前缀列),优化器也可能选择使用该索引进行跳跃扫描(skip scan),避免全表扫描,从而显著提升查询效率。( 不过,mysql 在 8.0 版本中就支持了这个功能 )
还有一些其他更新点,本文不再一一罗列,感兴趣的同学可以阅读一下官网文档。
PG 很努力、效果很显著!PostgreSQL 18 版本的以上更新,在 DB 领域虽然算不上是什么新技术,但是整体上对性能和管理方面的提升都是诚意十足、进步巨大的更新,我个人认为是一个值得升级尝试的版本,尤其是 AIO 的引入,对读密集型查询性能是一个质的提升!
https://www.postgresql.org/docs/18/ddl-generated-columns.html
https://www.postgresql.org/docs/current/dml-returning.html
https://medium.com/@rahulreza920/postgresql-18-why-this-is-the-biggest-database-update-in-years-52cd5935d2a0
https://www.bytebase.com/blog/what-is-new-in-postgres-18/
https://www.bytebase.com/blog/what-is-new-in-postgres-18-for-developer/
https://mp.weixin.qq.com/s/XKi5ybxNYINtQ_Ry3CBKMQ