由 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(1028370057
、1028370058
、1028370059
)分别被分配给由 SAVEPOINT s1
、s2
和s3
定义的子事务。因此,表中的元组具有不同的xmin
值(要了解更多关于xmin
的信息,请参阅 “5.5 系统列”),尽管它们都是在单个事务中被插入的。请注意,XID 1028370058
没有在xmin
值中使用,因为在savepoint s2
和savepoint s3
之间没有创建元组。总的来说,使用了四个 XID,而不是像没有子事务的情况下只使用一个 XID。
结论
上面的例子清楚地表明了两个可能不直观的事实:
- 分配给子事务的 XID 被用在元组头中,以用于参与 MVCC 元组可见性检查,尽管子事务的结果在主事务提交之前,对其他事务永远不可见(在 PostgreSQL 中,支持的“最低”隔离级别是
READ COMMITTED
)。 - 子事务会促进全局 XID 值(32 位值,需要特殊的自动维护,通常由 autovacuum 完成)的增长。因此,它隐式地增加了与 XID 回卷相关的风险:如果上述维护由于某种原因滞后,并且此问题未得到解决,则系统可能会触发事务 ID 回卷保护机制,将集群置于单用户模式的程度,从而导致长时间停机。有许多应用系统都因此发生过故障。比如一个应用,每秒可能有 1000 个写入事务,但如果它们都使用 10 个子事务,则 XID 每秒递增 10000 个。用户可能预料不到这一点,错误地以为子事务在每个事务中都有“局部” ID,而不会“浪费”全局 XID。实际情况却是,糟糕的 autovacuum 需要更频繁地在“事务 ID 回卷预防”模式下运行。
一句话:在子事务的积极使用和 XID 增长之间需要做好权衡。了解使用子事务的这种“代价”,对于避免在高负载系统中出现问题至关重要。