PostgreSQL 子事务问题: 事务 ID 增长

John Doe 五月 20, 2024

摘要:在本文中,我们将了解 PostgreSQL 中的子事务问题:事务 ID 增长。

目录

介绍

子事务的一个基本问题是:它们会递增 XID(全局事务 ID)。与常规事务一样,这仅在存在一些修改工作时才会发生。但如果有多个嵌套的子事务,则所有子事务都会分配自己的 XID,即使只是其中最内层的子事务发生了一次修改。

示例

在一个没有发生任何活动的数据库中,让我们来测试看看:

CREATE TABLE test1(i int);

BEGIN;

-- call txid_current() in PostgreSQL 12 or older
SELECT pg_current_xact_id();
 pg_current_xact_id
--------------------
         1028370056
(1 row)

INSERT INTO test1 SELECT 1;
SAVEPOINT s1;
INSERT INTO test1 SELECT 2;
SAVEPOINT s2;
SAVEPOINT s3;
INSERT INTO test1 SELECT 3;
COMMIT;

SELECT pg_current_xact_id();
 pg_current_xact_id
--------------------
         1028370060
(1 row)

SELECT ctid, xmin, xmax, i FROM test1;
 ctid  |    xmin    | xmax | i
-------+------------+------+---
 (0,1) | 1028370056 |    0 | 1
 (0,2) | 1028370057 |    0 | 2
 (0,3) | 1028370059 |    0 | 3
(3 rows)

在这个例子中,主事务有XID = 1028370056,而附加的 XID(102837005710283700581028370059)分别被分配给由 SAVEPOINT s1s2s3定义的子事务。因此,表中的元组具有不同的xmin值(要了解更多关于xmin的信息,请参阅 “5.5 系统列”),尽管它们都是在单个事务中被插入的。请注意,XID 1028370058 没有在xmin值中使用,因为在savepoint s2savepoint s3之间没有创建元组。总的来说,使用了四个 XID,而不是像没有子事务的情况下只使用一个 XID。

结论

上面的例子清楚地表明了两个可能不直观的事实:

  1. 分配给子事务的 XID 被用在元组头中,以用于参与 MVCC 元组可见性检查,尽管子事务的结果在主事务提交之前,对其他事务永远不可见(在 PostgreSQL 中,支持的“最低”隔离级别是READ COMMITTED)。
  2. 子事务会促进全局 XID 值(32 位值,需要特殊的自动维护,通常由 autovacuum 完成)的增长。因此,它隐式地增加了与 XID 回卷相关的风险:如果上述维护由于某种原因滞后,并且此问题未得到解决,则系统可能会触发事务 ID 回卷保护机制,将集群置于单用户模式的程度,从而导致长时间停机。有许多应用系统都因此发生过故障。比如一个应用,每秒可能有 1000 个写入事务,但如果它们都使用 10 个子事务,则 XID 每秒递增 10000 个。用户可能预料不到这一点,错误地以为子事务在每个事务中都有“局部” ID,而不会“浪费”全局 XID。实际情况却是,糟糕的 autovacuum 需要更频繁地在“事务 ID 回卷预防”模式下运行。

一句话:在子事务的积极使用和 XID 增长之间需要做好权衡。了解使用子事务的这种“代价”,对于避免在高负载系统中出现问题至关重要。