PostgreSQL 17: 不再支持 snapshot too old

John Doe 六月 4, 2025

你希望产生 “快照过旧” 的错误吗?遗憾的是,PostgreSQL 新版本不再支持了。

在山坡上奔跑的大象

特性提交日志

移除 “快照过旧” 特性。

移除用于产生 “快照过旧” 错误的old_snapshot_threshold设置和机制。遗憾的是,它在正确性和性能方面存在多个已知问题,这些问题主要是由社区开发者在进行快照扩展性的工作过程中发现和报告的。经过长时间未能制定有效修复计划后,我们决定将其移除。

这固然是一项理想的特性,未来可能会有人提出新的或改进的实现方案。

  • 讨论:https://postgr.es/m/CACG%3DezYV%2BEvO135fLRdVn-ZusfVsTY6cH1OZqWtezuEYH6ciQA%40mail.gmail.com
  • 讨论:https://postgr.es/m/20200401064008.qob7bfnnbu4w5cw4%40alap3.anarazel.de
  • 讨论:https://postgr.es/m/CA%2BTgmoY%3Daqf0zjTD%2B3dUWYkgMiNDegDLFjo%2B6ze%3DWtpik%2B3XqA%40mail.gmail.com

示例

在以前版本的 PostgreSQL 中,有一项名为“快照过旧”的特性。虽然 PostgreSQL 的每个版本都会推出一系列新特性,但有时也会删除一些特性。在新版本的 PostgreSQL 中,不会再出现“快照过旧”的情况。在探讨删除该特性的原因之前,我们先来了解一下它的含义。

此特性的主要目标是减少表膨胀,文档中对此有很好的描述。

默认情况下,该特性是禁用的:

postgres=# select version();
                                          version
-------------------------------------------------------------------------------------------
 PostgreSQL 16.5 on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
(1 row)

postgres=# \dconfig *snapshot*
List of configuration parameters
       Parameter        | Value
------------------------+-------
 old_snapshot_threshold | -1
(1 row)

要查看其实际效果,我们可以执行下面操作,通过将时间设置为 1 分钟来启用该功能:

select context from pg_settings where name = 'old_snapshot_threshold';
  context
------------
 postmaster
(1 row)

alter system set old_snapshot_threshold = 1;
$ pg_ctl restart
waiting for server to shut down.... done
server stopped
waiting for server to start.... done
server started
show old_snapshot_threshold;
 old_snapshot_threshold
------------------------
 1min
(1 row)

我们将old_snapshot_threshold设置为 1 分钟,现在我们可以轻松产生“快照过旧”的错误。具体步骤如下:

  • 在第一个会话中创建一个表
  • 在第二个会话中启动一个事务,并从该表中查询数据
  • 在第一个会话中插入、更新和删除表中的所有数据。第二个会话中的事务在此之前已经启动,因此它永远不会看到任何行,因为所有内容都已删除,但尚未提交。
  • 在第三个会话中,在短暂等待 1 分钟之后,进行一次 VACUUM(您可以使用 pg_sleep 来添加延迟)
  • 在第二个会话中也添加一些延迟,然后从表中查询数据
-- 会话 1
postgres=# create table t ( a int );
CREATE TABLE

-- 会话 2
postgres=# begin transaction isolation level repeatable read ;
BEGIN
postgres=*# select * from t;
 a 
---
(0 rows)

-- 会话 1
postgres=# begin;
BEGIN
postgres=*# insert into t select * from generate_series(1,1000);
INSERT 0 1000
postgres=*# update t set a = 3;
UPDATE 1000
postgres=*# delete from t;
DELETE 1000
postgres=*# commit;
COMMIT

-- 会话 3
postgres=# select pg_sleep(50); vacuum t;

-- 会话 2
postgres=*# select pg_sleep(50);
 pg_sleep 
----------

(1 row)

postgres=*# select * from t;
ERROR:  snapshot too old
postgres=!# 

这意味着 VACUUM 已经清理了这些行,并触发了“快照过旧”错误。这原本是针对长时间运行的事务或报告性查询的最后一道防线。在新版本的 PostgreSQL 中,这种情况不再可能发生。

参考

提交日志:https://git.postgresql.org/pg/commitdiff/97957fdbaa429c7c582d4753b108cb1e23e1b28a