PostgreSQL 每个大版本的发布都会带来一批值得关注的新特性。但对于生产环境而言,升级数据库的价值并不仅仅在于获得新的 SQL 语法或者新的功能,更重要的是能否解决现有版本在性能、并发、稳定性以及运维管理方面长期存在的问题。
回顾 PostgreSQL 过去几个版本的发展,可以看到一个比较明显的趋势:数据库正在逐步向更加智能化的资源管理、更低侵入性的在线维护、更完善的数据一致性保障以及更加灵活的运行机制演进。
例如,PostgreSQL 18 引入了异步 I/O 基础设施,为数据库的 I/O 并发处理提供了新的实现方式;而 PostgreSQL 19 则进一步增强了内置 AIO Worker 的动态管理能力。再比如,PostgreSQL 传统的 VACUUM、CLUSTER 等维护操作,在某些场景下仍然需要面对较重的锁竞争,而 PostgreSQL 19 引入的 REPACK 命令,则试图让表数据重组这类维护操作更加接近真正的在线化。
除此之外,多事务成员存储、逻辑复制冲突检测、查询执行计划控制以及 SSL 证书管理等方面,也都出现了值得关注的改进。
本文从数据库生产运维和实际应用场景出发,选取 PostgreSQL 19 中几个值得关注的场景,分析这些特性解决的问题、背后的实现思路,以及它们能够给现有 PostgreSQL 生产系统带来的实际价值。
在 PostgreSQL 中,随着业务持续运行,表和索引可能逐渐产生膨胀。
例如:
对于这类问题,PostgreSQL 提供了 VACUUM FULL 和 CLUSTER 操作。
但是,这些操作存在一个比较明显的运维问题:需要获取 ACCESS EXCLUSIVE 锁。
例如:
VACUUM FULL orders; CLUSTER orders USING orders_pkey;
VACUUM FULL 会重写整张表,并在操作期间持有 ACCESS EXCLUSIVE 锁。这意味着其他会话无法正常读取和修改目标表。
对于大型业务表而言,整个操作可能持续数十分钟甚至数小时。如果在业务高峰期执行,很容易造成业务请求排队、连接堆积,甚至引发业务超时。
虽然 PostgreSQL 生态中已经存在 pg_repack、pg_squeeze 等扩展,可以实现相对在线的表重组,但它们需要额外安装、维护,并且涉及独立的执行机制。
PostgreSQL 19 引入了新的 REPACK 命令,将表重组能力整合进数据库内核中。
融合了 pg_repack 与 pg_squeeze 的实现
基本使用方式:
REPACK orders;
如果希望尽量减少对业务读写的影响,可以使用:
REPACK (CONCURRENTLY) orders ;
REPACK 的主要用途包括:
例如:
REPACK (CONCURRENTLY) orders USING INDEX orders_pkey ;
通过指定索引,可以按照索引顺序重新组织表中的数据,从而改善特定查询场景下的数据访问效率。
注意 CONCURRENTLY 使用限制:
REPACK (CONCURRENTLY)不再需要像 VACUUM FULL 那样,在整个重组过程中持有 ACCESS EXCLUSIVE 锁。
其核心思路为:
整个过程中,大部分工作是在 SHARE UPDATE EXCLUSIVE 锁下完成的,ACCESS EXCLUSIVE 锁主要用于最后的切换阶段。这意味着,数据库不再需要为了重组一张大型业务表而长时间阻塞所有读写操作。
需要注意的是,CONCURRENTLY 并不意味着完全没有锁,也不意味着操作一定不会失败。
同时需要关注以下问题:
尤其需要注意长事务和快照的影响,CONCURRENTLY 模式在真正扫描堆表之前,需要建立逻辑解码环境,而这其中存在可能长时间等待的操作,而目前从 pg_stat_progress_repack 进度视图的 phase 无法直接观测,未来 v20 版本也许会有这个观测能力。
Autovacuum 是 PostgreSQL 自动维护机制中非常重要的一部分。
它主要负责:
对于写入频繁的业务表,Autovacuum 的执行效率会直接影响数据库的长期运行状态。
尤其是热点表,可能同时具有以下特点:
虽然 PostgreSQL 早已支持手动 VACUUM 的并行索引清理,但 Autovacuum 在自动维护场景下缺少相应的并行能力。
因此,当一张热点表拥有多个索引时,索引清理阶段可能成为整个 VACUUM 过程中的瓶颈。
PostgreSQL 19 将并行 VACUUM 的能力扩展到了 Autovacuum。
新增的主要配置参数包括:
autovacuum_max_parallel_workers = 4
同时支持通过表级存储参数进行覆盖设置:
ALTERTABLE orders SET ( autovacuum_parallel_workers =4 );
需要特别注意:当前并行处理主要发生在索引清理相关阶段,而不是整个过程。
对于包含多个大型索引的表,多个 Worker 可以分别处理不同的索引,从而缩短索引维护所需要的时间。
另外 Autovacuum 并行特性还优化了 Leader 和 Workers 共用一份 vacuum_cost_limit 配置,而不是每个进程分别消耗完整的 cost limit,否则并行清理实际消耗的 I/O 成本可能远远超过用户设定的 vacuum_cost_limit 预期值。
相比并行索引清理,另一个值得关注的改进是 Autovacuum 的任务调度策略。
在过去的版本中,Autovacuum 主要依据各类触发阈值判断哪些表需要执行维护。
但在实际生产环境中,不同表的维护紧迫程度并不相同。
例如:
如果仅仅根据是否达到触发阈值进行处理,很难充分体现不同表之间的维护优先级。
PostgreSQL 19 引入了基于 Score 的 Autovacuum 优先级排序机制。
新增的配置参数包括:
autovacuum_freeze_score_weight = 1 autovacuum_multixact_freeze_score_weight = 1 autovacuum_vacuum_score_weight = 1 autovacuum_vacuum_insert_score_weight = 1 autovacuum_analyze_score_weight = 1
这些参数分别用于调整不同维护需求在优先级计算中的权重:根据普通事务年龄压力、多事务年龄压力、死元组压力、纯插入元组的压力以及统计信息收集等因素计算分数,再按照分数对需要维护的表进行排序。
DBA 可以通过调整上面五类场景的 Score Weight,改变各类维护需求在优先级计算中的相对重要性。
PostgreSQL 19 还增加了一个新的观测视图:
SELECT relname, round(score::numeric,2) AS score FROM pg_stat_autovacuum_scores WHERE schemaname ='public' ORDERBY score DESC;
通过该视图可以观察当前数据库中各个表的 Autovacuum 优先级分数,为分析维护调度情况提供依据。
score = max (analyze_score, xid_score, mxid_score, vacuum_insert_score, vacuum_score)
在 PostgreSQL 中,多事务 MultiXact 主要用于记录多个事务对同一行数据的锁定关系。
例如:
-- 会话1 BEGIN; SELECT*FROM accounts WHERE id =100 FOR SHARE; -- 会话2 BEGIN; SELECT*FROM accounts WHERE id =100 FOR SHARE; -- 会话3 UPDATE accounts SET balance = balance +100 WHERE id =100;
当多个事务并发对同一行数据进行加锁时,PostgreSQL 需要记录多个事务成员的信息。
由于 Tuple Header 中的 xmax 字段无法直接保存所有持有行锁的事务 ID,因此 PostgreSQL 引入了 MultiXact 机制,在 Tuple Header 中保存多事务号,而具体的事务成员信息则持久化在:
$PGDATA/pg_multixact/ ├── offsets/ └── members/
其中:
当数据库需要判断一行数据的锁状态、事务可见性或者是否存在并发更新冲突时,就可能需要读取多事务信息。
注意区分两个概念:
在 PostgreSQL 18 及以前的版本中,多事务成员存储的偏移量 MultiXactOffset 使用 32 位无符号整数,在如下场景存在多事务成员持续异常增长的隐患:
PostgreSQL 19 将 MultiXactOffset 从 32 位加宽到 64 位,并不是将 MultiXact ID 多事务号变为 64 位。
通过将多事务成员存储偏移量加宽到 64 位,解决了原先 32 位成员偏移量的容量限制。对于高并发、多事务号产生速率较高的系统,能够获得更大的成员存储地址空间,减少未来成员存储容量成为系统瓶颈的风险。
PostgreSQL 18 引入了异步 I/O(Asynchronous I/O)机制,则允许数据库将 I/O 请求提交后继续执行其他工作,从而提高 I/O 并发能力。
PostgreSQL 18 支持不同的 I/O 实现方式,其中 Worker 模式通过后台 Worker 处理异步 I/O 请求,但应该启动多少个 I/O Worker 才最合适呢?
如果 Worker 数量设置过少,可能无法充分发挥存储设备的并发能力。
如果设置过多,又可能产生不必要的进程资源消耗和调度开销。
而且数据库的 I/O 负载并不是固定的:
因此,固定数量的 Worker 很难适应动态变化的数据库负载。
PostgreSQL 19 在 io_method = worker 模式下可自动管理所需的后台 I/O Worker 数量。
新增的配置参数包括:
io_min_workers = 2 io_max_workers = 16 io_worker_idle_timeout = 60s io_worker_launch_interval = 10ms
这些参数分别用于控制:
| 参数 | 作用 |
|---|---|
| io_min_workers | 最少保留的 I/O Worker 数量 |
| io_max_workers | 最多允许启动的 I/O Worker 数量 |
| io_worker_idle_timeoutWorker | 空闲后的超时时间 |
| io_worker_launch_interval | Worker 启动的时间间隔控制 |
通过这些参数,PostgreSQL 可以根据当前 I/O 工作需求,动态增加或减少 Worker 数量。
其基本工作过程为:
这项改进的意义不仅仅是减少 DBA 的配置工作,更重要的是,数据库开始具备根据实际负载自动管理 I/O 执行资源的能力。 PostgreSQL 19 让 AIO 从单纯的异步 I/O 执行机制,进一步演进为具备动态 Worker 资源管理能力的子系统。
在 PostgreSQL 主备架构中,为了降低 Primary 节点的查询压力,通常会将只读请求分发到 Standby 节点。
例如:
如果使用同步流复制,意味着主库提交事务可能受到备库响应速度的影响。因此,有些生产系统倾向于使用异步流复制。
但异步流复制存在一个问题:Primary 节点事务已经提交,Standby 节点却不一定已经回放这笔事务。
PostgreSQL 19 引入了 WAIT 命令:
WAIT FOR LSN 'lsn';
通过 WAIT 命令,应用程序可以要求数据库等待指定 lsn 到达目标状态。
支持的等待模式包括:
| 模式 | 含义 |
|---|---|
| standby_replay | 等待备库完成 WAL 回放 |
| standby_write | 等待备库将 WAL 写入操作系统 |
| standby_flush | 等待备库将 WAL 刷入持久化存储 |
| primary_flush | 等待主库将 WAL 刷入持久化存储 |
其中,standby_replay 是实现读己之写的关键。
参考示例如下:
UPDATE orders SET status ='PAID' WHERE order_id =10001;
SELECT pg_current_wal_insert_lsn();
应用程序确保获取到的 LSN 不早于目标事务 COMMIT 记录的结束位置。
官方文档给出的示例中,通过在事务提交后获取 WAL 插入位置,再将该位置传递给备库。
WAIT FOR LSN '0/0306EE20' WITH ( MODE 'standby_replay', TIMEOUT '5s' );
SELECT status FROM orders WHERE order_id =10001;
当 WAIT 成功返回时,意味着备库已经完成了指定 LSN 的 WAL 回放。
此时再执行查询,就可以确保看到该 LSN 之前已经提交的相关数据变更。
传统方式下,应用程序如果希望判断备库是否已经追上主库,可能会采用以下方式:
这些方式通常需要应用程序自行维护轮询逻辑。
而 WAIT 命令可以直接将目标 LSN 作为等待条件,应用程序不再需要持续轮询,而是由数据库负责等待目标状态达到。
因此,从应用程序的角度看,这是一种比较接近事件驱动的等待方式。
注意事项:
在 PostgreSQL 主备架构中,当 Primary 发生故障,Standby 被提升为新的 Primary 后,原来的 Primary 旧主与新主已经产生不同的 WAL 时间线,旧主不能简单地重新启动并继续作为备库使用。
此时通常有两种选择:使
对于数据量较大的数据库,重新执行完整基础备份可能需要较长时间。
因此,pg_rewind 在主备切换后的快速重建过程中具有非常重要的作用。
pg_rewind 并不是简单地将新主的数据文件全部复制到旧主。它的核心思路:只识别并同步两个集群时间线分叉后,目标集群 发生变化的数据块。
其主要过程为:
pg_rewind 使用参考示例:
pg_rewind \ --target-pgdata=/data/old_primary \ --source-server="host=new_primary port=5432 user=postgres dbname=postgres"
对于旧主重建而言,pg_rewind 可以避免完整复制所有数据库数据文件。
而 pg_rewind 的执行效率与以下因素密切相关:
对于大型数据库,如果时间线分叉后产生了大量 WAL,pg_rewind 需要进行较多的 WAL 扫描和数据块定位工作。
PostgreSQL 19 在保证安全的情况下,优化了 pg_rewind 拷贝 WAL 文件处理逻辑:在确定时间线分叉后,对于分叉点之前旧主和新主共同存在且大小一致的 WAL 段文件,不再进行重复拷贝;仅拷贝包含分叉点及其之后的必要 WAL。
对于大型数据库和对故障恢复时间有严格要求的 HA 集群,pg_rewind 的效率提升具有实际运维价值,可以进一步缩短旧主重建和集群冗余恢复的时间。
PostgreSQL 的查询优化器采用基于成本的优化策略,但是优化器并不总能做出正确的判断。
例如:一条 SQL 在数据量较小时执行正常,但随着数据量增长,原来的执行计划可能不再适合当前数据分布。
又或者统计信息不准确,导致优化器错误估算某个条件的选择率,从而选择了不合理的 Join 顺序或扫描方式。
对于生产环境而言,执行计划突然发生变化,可能造成查询性能从毫秒级下降到数十秒甚至数分钟。
PostgreSQL 19 引入了 pg_plan_advice 扩展,用于描述、复现和调整优化器的关键决策。
当 EXPLAIN 命令在开启 PLAN_ADVICE 后,PostgreSQL 可以输出当前执行计划对应的 Advice 信息。
除了 pg_plan_advice,PostgreSQL 19 还提供了 pg_stash_advice。
它可以根据查询标识符等信息,自动应用预先保存的 Plan Advice。
这意味着,执行计划管理可以从人工为每条 SQL 配置 Advice,进一步向自动化管理演进。
对于生产环境中大量重复执行的 SQL,可以根据 Query ID 等信息关联相应的 Advice,减少人工干预。
另外 Advice 也需要谨慎使用,如果数据分布发生变化,原本合理的执行计划可能不再适用。过度约束优化器,反而可能导致更差的执行计划。
在传统 PostgreSQL 部署中,通常一个实例配置一套 SSL 证书。
例如:
ssl = on ssl_cert_file = 'server.crt' ssl_key_file = 'server.key'
当客户端通过 SSL/TLS 连接数据库时,服务器使用配置的证书完成身份验证。
但在一些云数据库、多租户数据库平台以及统一数据库接入服务中,一个 PostgreSQL 实例可能需要同时服务多个域名。
例如:
db-a.example.com db-b.example.com db-c.example.com
这些域名可能分别属于不同的业务系统、客户或者租户。
如果所有连接都使用同一套 SSL 证书,就可能出现证书身份与客户端连接域名不匹配的问题。
过去通常需要通过独立实例、代理层或者其他方式解决。
标准的解决方案为 SNI(全称为 Server Name Indication),它是 TLS 协议中的一个扩展。
SNI 允许客户端在 TLS 握手阶段,通过消息向服务器声明自己希望访问的域名。
例如客户端连接:
db-a.example.com:5432
在 TLS 握手阶段,客户端可以通过 SNI 告诉服务器:
server_name = db-a.example.com
服务器就可以根据客户端提供的域名,选择对应的 SSL 证书。
这样,同一个服务端就可以根据不同域名提供不同的证书。
PostgreSQL 19 增加了服务器端 SNI 支持,相关配置文件为:
$PGDATA/pg_hosts.conf
该配置文件用于定义不同主机名与对应密钥、证书之间的映射关系。
其核心思路是:
如此,一个 PostgreSQL 实例就能够根据不同的连接域名,选择不同的 SSL 证书。
注意:SNI 解决的是 TLS 握手期间的证书选择问题,并不等同于数据库租户隔离,也不能代替 pg_hba.conf、角色权限和数据库访问控制。
PostgreSQL 逻辑复制主要采用 发布/订阅 架构。
传统逻辑复制通常是单向复制:
Publisher | | Logical Replication | Subscriber
但是,在多节点写入架构中,不同节点可能同时修改同一条业务记录。
例如:
节点 A:
UPDATE accounts SET balance =1000 WHERE id =100;
节点 B:
UPDATE accounts SET balance =2000 WHERE id =100;
如果两个节点都产生本地修改,再通过逻辑复制将数据同步到对方,就可能出现数据冲突。
PostgreSQL 逻辑复制在应用 UPDATE 和 DELETE 时,会根据目标行是否存在、是否满足更新条件等情况识别部分冲突。
但是,对于某些已经被本地事务删除的行,或者已经被 VACUUM 清理的旧版本行,订阅端可能无法获得足够的信息来准确判断冲突的来源。
PostgreSQL 19 为订阅增加了 retain_dead_tuples 选项(早期阶段名称叫 retain_conflict_info):
可以在创建订阅时使用:
CREATE SUBSCRIPTION my_sub CONNECTION 'host=x.x.x.x port=5432 dbname=postgres user=replicator password=xxx' PUBLICATION my_pub WITH ( retain_dead_tuples =true );
也可以通过 ALTER SUBSCRIPTION 修改:
ALTER SUBSCRIPTION my_sub SET ( retain_dead_tuples =true );
这项机制的核心目的,是在逻辑复制应用过程中保留冲突检测所需要的死元组及相关事务信息。
在启用该选项后,订阅端会维护冲突检测所需的事务信息保留机制,使发布端相关的旧版本记录及事务元数据不会过早被清理。
如此,在处理远端 UPDATE 和 DELETE 时,就能够利用更多历史信息判断冲突。
当前这项特性主要涉及以下冲突类型:
| 冲突类型 | 含义 |
|---|---|
| update_deleted | 远端 UPDATE 对应的记录已经被本地删除 |
|update_origin_differs|UPDATE 涉及的事务来源与预期不同|
|delete_origin_differs|DELETE 涉及的事务来源与预期不同|
注意事项:retain_dead_tuples 并不是没有代价的。
既然需要保留死元组,就意味着部分旧版本记录不能像普通场景那样立即被 VACUUM 清理。
如果订阅端复制应用延迟较大,可能导致:
因此,在生产环境中启用该选项,需要重点监控:
另外,订阅端的 retain_dead_tuples 功能对发布端也有版本和状态要求,不能简单认为只要订阅端升级到 PostgreSQL 19 就可以无条件启用。
对于多节点写入、双向逻辑复制以及需要检测跨节点 UPDATE/DELETE 冲突的架构而言,retain_dead_tuples 是一个非常值得关注的能力。它让 PostgreSQL 能够保留过去可能被 VACUUM 提前清理的历史信息,为更准确的冲突检测提供基础。
从上面几个技术点可以看到,PostgreSQL 19 的改进涉及数据库多个重要子系统的能力增强。
从生产环境的实际价值出发,可以将这些改进归纳为以下几个方面。
| 技术方向 | PostgreSQL 19 改进 | 解决的核心问题 |
|---|---|---|
| 在线维护 | REPACK (CONCURRENTLY) | 大型表重组期间的业务阻塞 |
| 自动维护 | Autovacuum 并行索引清理 | 大型索引维护效率 |
| 智能调度 | Autovacuum Score | 不同表之间的维护优先级 |
| 事务管理 | 64 位 MultiXactOffset | 多事务成员存储容量限制 |
| I/O 管理 | AIO Worker 自动伸缩 | Worker 配置的资源管理问题 |
| 写读一致性 | WAIT FOR LSN | 异步复制环境下的读己之写 |
| 高效运维 | pg_rewind 相关优化 | 主备切换后的旧主重建效率 |
| SQL 优化 | pg_plan_advice | 执行计划稳定性管理 |
| 安全连接 | SNI | 多域名场景下的 SSL 证书选择 |
| 逻辑复制 | retain_dead_tuples | 多节点 UPDATE/DELETE 冲突检测 |
从数据库发展趋势来看,PostgreSQL 19 有几个特别值得关注的方向。
REPACK (CONCURRENTLY) 以及 Autovacuum 并行索引清理,都在降低数据库日常维护对业务的影响。
无论是 Autovacuum 的优先级打分策略,还是 AIO Worker 的动态伸缩,这都体现出资源管理逐渐从人工固定配置向动态管理演进。
WAIT FOR LSN 让异步流复制也能够支持特定业务请求的读己之写一致性。相比同步流复制,这种按需等待的方式更有利于兼顾性能与一致性。
SNI 解决多域名证书选择问题,advice 语义增强执行计划管理能力,retain_dead_tuples 则为复杂逻辑复制架构中的冲突检测提供支持。
这些能力让 PostgreSQL 不仅能够更好地服务传统单主单备架构,也能够更灵活地适应云数据库、多租户以及多节点数据同步等场景。(几乎所有的云厂商都提供了 PostgreSQL 服务)
当然,升级一个数据库大版本,不能只看新特性,还需要综合评估兼容性、扩展支持、运维工具适配以及升级成本。
对于正在使用 PostgreSQL 10、11、12、13、14、15、16、17 或 18 的用户而言,可以提前关注 PostgreSQL 19 的这些改进,结合自身业务痛点制定升级计划。
升级 PostgreSQL 19 的真正价值,不只是获得一个新的数据库内核,而是让数据库在维护、并发、I/O、复制和运维管理等方面拥有更好的能力。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70043484/viewspace-3188892/ ,如需转载,请注明出处,否则将追究法律责任。