由 John Doe 十月 9, 2025
摘要:你有没有在 PostgreSQL 中遇到过多个 autovacuum 进程不停工作的情况?此时,系统中的大部分资源都被用来进行 VACUUM。在本文中,我们将了解 PostgreSQL 垃圾清理的高成本和非确定性,以及相关的解决方案。
目录
旧行版本清理的用例
下面我们在 PostgreSQL 的数据库中,创建一个表,插入一些数据,并对表数据进行清理和分析。
-- 会话一
CREATE TABLE t_large (id integer, name text);
INSERT INTO t_large (id, name)
SELECT i, repeat('Pg', 64)
FROM generate_series(1, 1000000) AS s(i);
CREATE INDEX large_idx ON t_large (id);
VACUUM ANALYZE t_large;
在准备好基础表和初始数据后,让我们启动一个会话,在该会话中开始一个事务,并保持在空闲状态:
-- 会话二
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT name FROM t_large WHERE id = 202510;
让我们对表中的一部分数据进行一些更新,当然,也可以删除一部分数据。
-- 会话一
UPDATE t_large SET name = 'dummy' WHERE mod(id, 100) = 1;
DELETE FROM t_large WHERE mod(id, 100) = 50;
上面的更新和删除操作,表中已经产生了一定量的旧行版本。现在,我们来对该表进行 VACUUM 清理,以观察 autovacuum 进程清理旧行版本的过程:
-- 会话一
VACUUM (verbose) t_large;
INFO: vacuuming "postgres.public.t_large"
INFO: finished vacuuming "postgres.public.t_large": index scans: 0
pages: 0 removed, 20409 remain, 20409 scanned (100.00% of total)
tuples: 0 removed, 1010000 remain, 20000 are dead but not yet removable
removable cutoff: 753, which was 2 XIDs old when operation ended
frozen: 0 pages from table (0.00% of total) had 0 tuples frozen
index scan not needed: 0 pages from table (0.00% of total) had 0 dead item identifiers removed
avg read rate: 0.000 MB/s, avg write rate: 0.000 MB/s
buffer usage: 40833 hits, 0 misses, 0 dirtied
WAL usage: 1 records, 0 full page images, 188 bytes
system usage: CPU: user: 0.12 s, system: 0.00 s, elapsed: 0.12 s
...
上面的 VACUUM 操作扫描了全表 20409 个页面,没有清理任何的旧行版本。由于会话二中的事务还在进行中,系统需要保留旧行版本以对该事务保持可见性,会话一的 VACUUM 操作只会做无用功,不会清理任何的垃圾数据。
对于后台 autovacuum 进程定期调度执行的 VACUUM 动作,同样也会出现上面的问题,它会频繁消耗存储的 IO 资源,实际上没有清理掉任何的旧行版本。而且,随着后续不断地进行更新和删除操作,系统产生的旧行版本会持续积累,最终会给系统造成如同雪崩的效应。
旧行版本清理的场景分析
PostgreSQL 采用多版本并发控制(MVCC)机制,当数据被更新或删除时,原始记录不会立即移除,而是标记为不可见,以支持事务回滚和快照读,这就产生了旧行版本。例如,在一个订单状态频繁更新的订单表中,每次更新都会产生旧行版本。
在数据量庞大且更新频繁的场景下,旧行版本清理成本会很高。因为清理过程(VACUUM)需要扫描大量数据,判断哪些行版本不再需要,并回收空间。此外,如果 autovacuum 机制配置不当,无法及时清理死元组,也会导致清理成本增加,表膨胀严重。
要降低旧行版本的清理成本,可以考虑采用以下措施:
- 定期执行 VACUUM 操作:合理安排 VACUUM 的执行时间和频率,例如在业务低峰期执行,减少对业务的影响。同时,可以根据表的特点选择合适的 VACUUM 方式,如普通 VACUUM 清理死元组,VACUUM FULL 可释放磁盘空间但会阻塞访问。
- 优化 autovacuum 参数:调整 autovacuum_vacuum_cost_delay 和 autovacuum_naptime 等参数,使 autovacuum 进程能够更及时、高效地清理旧行版本。例如,适当降低 autovacuum_vacuum_cost_delay,提高清理速度。
结论
在本文中,我们了解了 PostgreSQL 清理旧行版本可能会出现的雪崩现象,以及缓解该问题的可能措施。
另外,Redrock Postgres 引入了回滚日志,系统可以通过在新行版本上应用回滚日志按需生成旧行版本。另外,在执行删除或非就地更新的事务提交后,会立即重用空间。因而在更新或删除表数据后,并不需要定期触发执行旧行版本的清理工作。