为什么vacuum没有移除死元组?(译)

译自:https://www.cybertec-postgresql.com/en/reasons-why-vacuum-wont-remove-dead-rows/

为什么vacuum?

每当更新或删除PostgreSQL 表中的行时,都会留下死行。vacuum可以解决他们,以便空间可以重复使用。如果表没有被清空,它将变得膨胀,这会浪费磁盘空间并减慢顺序表扫描(以及 - 在较小程度上 - 索引扫描)。
VACUUM 还负责freezing表行,以避免在事务ID计数器回卷时出现问题,但那是另一回事。
通常你不必处理所有这些问题,因为PostgreSQL内置的autovacuum daemon会为你做到这一点。要了解有关启用和禁用autovacuum的更多信息,请阅读这篇文章

vacuum解决表膨胀

如果您的表变得膨胀,您首先要检查的是autovacuum是否处理了它们:

SELECT schemaname, relname, n_live_tup, n_dead_tup, last_autovacuum
FROM pg_stat_all_tables
ORDER BY n_dead_tup
/ (n_live_tup
* current_setting('autovacuum_vacuum_scale_factor')::float8
+ current_setting('autovacuum_vacuum_threshold')::float8)
DESC
LIMIT 10;

如果此处未显示膨胀的表,n_dead_tup为零,last_autovacuum为 NULL,则统计信息收集可能有问题
如果膨胀的表就在顶部,但last_autovacuum为 NULL,您可能需要将autovacuum配置得更激进,以便完成表上的工作。
但有时结果会如下所示:

schemaname | relname | n_live_tup | n_dead_tup | last_autovacuum
------------+--------------+------------+------------+---------------------
laurenz | vacme | 50000 | 50000 | 2018-02-22 13:20:16
pg_catalog | pg_attribute | 42 | 165 |
pg_catalog | pg_amop | 871 | 162 |
pg_catalog | pg_class | 9 | 31 |
pg_catalog | pg_type | 17 | 27 |
pg_catalog | pg_index | 5 | 15 |
pg_catalog | pg_depend | 9162 | 471 |
pg_catalog | pg_trigger | 0 | 12 |
pg_catalog | pg_proc | 183 | 16 |
pg_catalog | pg_shdepend | 7 | 6 |
(10 rows)

这里最近运行了autovacuum,但它并没有释放死元组!
我们可以通过运行 VACUUM (VERBOSE) 来验证问题:

test=> VACUUM (VERBOSE) vacme;
INFO: vacuuming "laurenz.vacme"
INFO: "vacme": found 0 removable, 100000 nonremovable row versions in
443 out of 443 pages
DETAIL: 50000 dead row versions cannot be removed yet,
oldest xmin: 22300
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.01 s, system: 0.00 s, elapsed: 0.01 s.

为什么vacuum没有移除死元组?

VACUUM 只能删除不再需要的行版本(也称为“元组”)。如果删除事务的事务ID(存储在xmax系统列中)早于 PostgreSQL 数据库中仍处于活动状态的最旧事务,则不需要这个元组。(或者,用于整个集群的共享表)。
这个值(上面vacuum输出中的22300)称为“xmin horizon”。
在PostgreSQL集群中,有三件事可以停止这个xmin horizon:
1.长事务
你可以通过以下查询找到进程及其 xmin 值:

SELECT pid, datname, usename, state, backend_xmin, backend_xid
FROM pg_stat_activity
WHERE backend_xmin IS NOT NULL OR backend_xid IS NOT NULL
ORDER BY greatest(age(backend_xmin), age(backend_xid)) DESC;

你可以使用pg_terminate_backend()函数中止阻塞VACUUM的会话。
2. 遗弃的复制槽
复制槽是一种数据结构,可防止PostgreSQL丢弃从库仍需要的信息以赶上主库。
如果复制延迟或从库关闭,复制槽将阻止VACUUM删除旧行。
你可以通过以下查询找到所有复制槽及其xmin值:

SELECT slot_name, slot_type, database, xmin
FROM pg_replication_slots
ORDER BY age(xmin) DESC;

使用pg_drop_replication_slot()函数删除不再需要的复制槽。
注意:仅当 hot_standby_feedback = on 时,物理复制才会发生这种情况。对于逻辑复制,存在相同的危险,但只有系统目录会受到影响。在这种情况下,请检查列catalog_xmin。
3. Orphaned prepared transactions
两阶段提交期间,首先使用PREPARE语句准备分布式事务,然后使用COMMIT PREPARE语句提交。
一旦事务准备好了,它就会一直“徘徊”,直到它被提交或中止。它甚至须在服务器重新启动后幸存下来!通常,事务不会长时间在prepared状态,但有时会出错,管理员必须手动删除prepared事务。
您可以通过以下查询找到所有准备好的事务及其 xmin 值:

SELECT gid, prepared, owner, database, transaction AS xmin
FROM pg_prepared_xacts
ORDER BY age(transaction) DESC;

使用ROLLBACK PREPAREDSQL语句删除prepared的事务。
4. 从库hot_standby_feedback = on
通常,设置流复制时主服务器不关心在备用服务器上运行的查询。因此,VACUUM将愉快地删除备用数据库上长时间运行的查询可能仍需要的死行,这可能会导致复制冲突。要减少复制冲突,可以在备用服务器上设置 hot_standby_feedback = on。然后,备用数据库将随时通知主数据库最早的打开事务,并且主数据库上的 VACUUM 不会删除备用数据库上仍需要的旧行版本。
若要找出所有备用服务器的 xmin,可以在主服务器上运行以下查询:

SELECT application_name, client_addr, backend_xmin
FROM pg_stat_replication
ORDER BY age(backend_xmin) DESC;

更多关于表膨胀和自动提交的博客

猜你喜欢

转载自blog.csdn.net/qq_40687433/article/details/130835224