3

我有一个非常大的表,400MM 记录,通常只接收插入。但是,最近我必须对记录进行大量更新才能完成任务。这会创建很多死元组。我已将全局配置更新为以下内容:

autovacuum_vacuum_scale_factor = 0
autovacuum_vacuum_threshold = 10000
autovacuum_vacuum_cost_limit = 2000
autovacuum_max_workers = 6

有了这些设置,我的意图是,每当死元组超过 10,000 条记录时,自动清理就会清理它。

但是,我发现当表忙于其他插入等时,死元组计数不会改变。它保持固定在某个死元组计数。只有当 db 活动在夜间减慢时,autovacuum 才能正常工作。

我需要 autovacuum 全天积极处理死元组。我将如何做到这一点?我需要增加 max_workers 数量吗?

更新:用户@Laurenz Albe 建议我运行一些带有和不带有死元组的性能数字来展示性能差异。

我将提供 sql 查询和 EXPLAIN(ANALYZE, BUFFERS) 结果。我更改了表的名称和组键以保护隐私。

EXPLAIN (ANALYZE, BUFFERS)
SELECT  ld.upid,
        MAX(ld.lid)
INTO _tt_test_with_dead_tuples
FROM big_table ld
GROUP BY ld.upid;

-- >>> 大约 1%(383.2MM 中的 3.648MM)死元组,结果如下。

HashAggregate  (cost=25579746.07..25584552.97 rows=480690 width=8) (actual time=5966760.520..5975279.359 rows=16238417 loops=1)
  Group Key: upid
  Buffers: shared hit=3015376 read=16753169 dirtied=1759802 written=1360458
  ->  Seq Scan on big_table ld  (cost=0.00..23642679.05 rows=387413405 width=8) (actual time=0.024..5593239.148 rows=383753513 loops=1)
        Buffers: shared hit=3015376 read=16753169 dirtied=1759802 written=1360458
Planning time: 2.677 ms
Execution time: 6012319.846 ms

-- >>> 有 0 个死元组,结果如下。

HashAggregate  (cost=25558409.48..25562861.52 rows=445204 width=8) (actual time=825662.640..835163.398 rows=16238417 loops=1)
  Group Key: upid
  Buffers: shared hit=15812 read=19753809
  ->  Seq Scan on big_table ld  (cost=0.00..23628813.32 rows=385919232 width=8) (actual time=0.020..533386.128 rows=383753513 loops=1)
        Buffers: shared hit=15812 read=19753809
Planning time: 10.109 ms
Execution time: 843319.731 ms
4

1 回答 1

3

死元组不是你的问题。

您真正的问题在其他地方;我在下面强调了它。

慢查询中的顺序扫描:

Buffers: shared hit=3015376 read=16753169 dirtied=1759802 written=1360458

快速查询中的顺序扫描:

Buffers: shared hit=15812 read=19753809

看起来大约有 200 万个表块包含最近写入或更新的元组。

在写入元组时,PostgreSQL 还不知道事务是否会提交或回滚,因此此信息不会存储在元组中。但是,它记录在存储在(或,取决于您的版本)中的提交日志中。pg_xactpg_clog

现在第一个出现并读取新写入的元组的读者将不得不查阅提交日志以确定元组是否“存在”。为了省去未来读者的麻烦,它在元组中设置了所谓的提示位来反映该信息。

这会改变并因此“弄脏”包含元组的块,如果您的shared_buffers设置很小并且可用缓冲区用完,后端甚至必须将块写入存储以清理它们并腾出空间。

这篇文章使您的查询如此缓慢。

清理表可以解决问题,因为VACUUM它不仅可以清理死元组,还可以为您设置提示位(它也是一个阅读器!)。

为了验证这一点,在SELECT清理表的情况下再次运行相同的操作,您将观察到它与 300 万个死元组一样快,因为现在提示位已全部设置。

这就是为什么VACUUM在加载了很多行之后在表上运行是个好主意的原因之一,即使没有什么要清理的——你可以为第一个读者节省很多工作。

想法:增加会shared_buffers改善情况吗?

但是由于清理表可以解决问题,您不妨使用 autovacuum 更频繁地设置提示位。

为此,您可以设置autovacuum_vacuum_scale_factor为 0 并设置autovacuum_vacuum_threshold为一个大常量(大于 10000),这样就不会有太多没有提示位的行

此外,设置autovacuum_vacuum_cost_delay为 0 以便自动清空快速完成。

不要全局更改这些参数,ALTER TABLE ... SET (...)仅用于为该表设置它们。

于 2018-09-04T18:13:12.870 回答