由 John Doe 六月 2, 2026
摘要:在本文中,我们将了解大事务造成的 PostgreSQL 存储空间膨胀的问题,及其产生的影响。
目录
准备基础数据
下面我们在 PostgreSQL 18.4 的数据库中,创建一个表,插入一些数据,并对表数据进行清理操作。
CREATE TABLE bloat (id int PRIMARY KEY, filler text);
INSERT INTO bloat (id, filler)
SELECT i, repeat('x', 128)
FROM generate_series(1, 10000000) AS s(i);
VACUUM ANALYZE bloat;
查看表和索引占用的空间大小:
SELECT relname, pg_size_pretty(pg_relation_size(oid))
FROM pg_class WHERE relname LIKE 'bloat%';
relname | pg_size_pretty
------------+----------------
bloat | 1594 MB
bloat_pkey | 214 MB
(2 rows)
执行批量更改的大事务
在准备好基础表和初始数据后,让我们开始一个事务,以执行批量更改的操作:
BEGIN;
UPDATE bloat SET filler = repeat('y', 128);
查看表和索引占用的空间大小:
SELECT relname, pg_size_pretty(pg_relation_size(oid))
FROM pg_class WHERE relname LIKE 'bloat%';
relname | pg_size_pretty
------------+----------------
bloat | 3189 MB
bloat_pkey | 428 MB
(2 rows)
事务结束后的空间回收
让我们结束上面启动的大事务。
COMMIT;
等待大约 10 分钟之后,查看表和索引占用的空间大小:
SELECT relname, pg_size_pretty(pg_relation_size(oid))
FROM pg_class WHERE relname LIKE 'bloat%';
relname | pg_size_pretty
------------+----------------
bloat | 3189 MB
bloat_pkey | 428 MB
(2 rows)
在进行全表更改和自动清理后,PostgreSQL 没有回收旧元组占用的空间。
Redrock Postgres 的表现
下面我们在 Redrock Postgres 的数据库中,执行和上面相同的测试用例。在执行批量更改操作之前,查看表和索引占用的空间大小,以及当前数据库中回滚段的使用情况:
SELECT relname, pg_size_pretty(pg_relation_size(oid))
FROM pg_class WHERE relname LIKE 'bloat%';
relname | pg_size_pretty
------------+----------------
bloat | 1447 MB
bloat_pkey | 216 MB
(2 rows)
SELECT oid, relname, pg_size_pretty(pg_relation_size(oid)) AS undsize
FROM pg_class WHERE relkind = 'u'
ORDER BY pg_relation_size(oid) DESC LIMIT 4;
oid | relname | undsize
------+------------+---------
8113 | pg_undo_13 | 1069 MB
8103 | pg_undo_3 | 1040 kB
8104 | pg_undo_4 | 1040 kB
8102 | pg_undo_2 | 1040 kB
(4 rows)
输出结果显示,前面的 INSERT 操作产生了 1069 MB 的回滚日志。接下来,执行相同的批量更改操作:
BEGIN;
UPDATE bloat SET filler = repeat('y', 128);
上面的 UPDATE 操作更改了全表 10000000 条记录。我们再次查看表和索引占用的空间大小,以及回滚段的使用情况:
SELECT relname, pg_size_pretty(pg_relation_size(oid))
FROM pg_class WHERE relname LIKE 'bloat%';
relname | pg_size_pretty
------------+----------------
bloat | 1447 MB
bloat_pkey | 216 MB
(2 rows)
SELECT oid, relname, pg_size_pretty(pg_relation_size(oid)) AS undsize
FROM pg_class WHERE relkind = 'u'
ORDER BY pg_relation_size(oid) DESC LIMIT 4;
oid | relname | undsize
------+------------+---------
8118 | pg_undo_18 | 1906 MB
8113 | pg_undo_13 | 1069 MB
8103 | pg_undo_3 | 1040 kB
8104 | pg_undo_4 | 1040 kB
(4 rows)
输出结果显示,前面的 UPDATE 操作产生了 1906 MB 的回滚日志。让我们结束上面启动的大事务。
COMMIT;
等待大约 1 小时之后,查看回滚段的使用情况:
SELECT oid, relname, pg_size_pretty(pg_relation_size(oid)) AS undsize
FROM pg_class WHERE relkind = 'u'
ORDER BY pg_relation_size(oid) DESC LIMIT 4;
oid | relname | undsize
------+-----------+---------
8102 | pg_undo_2 | 1040 kB
8103 | pg_undo_3 | 1040 kB
8104 | pg_undo_4 | 1040 kB
8101 | pg_undo_1 | 1040 kB
(4 rows)
在后台自动清理后,Redrock Postgres 能够完全收缩扩大的回滚段空间。
测试结论
在 PostgreSQL 中,autovacuum 后台进程会定期进行垃圾清理。不过,这种类型的VACUUM只是回收空间并使其可被重用,在大多数情况下,额外空间不会返还给操作系统;它只是保留在同一个表内供再次使用。
在 Redrock Postgres 中,事务修改大量行时,必然占用大量回滚段空间,这是无法避免的。尤其需注意涉及大量行的UPDATE或DELETE事务。多数情况下,若应用支持,分批执行更优(如每个事务修改 / 删除数千行)。不过,当某个回滚段的使用空间超出 1GB 以后,后台维护进程也会自动对其进行收缩,以回收其占用的额外空间。