我有一个很大的表,记录为400MM,通常只接收插入内容。但是,最近我必须对记录进行大量更新才能完成任务。这造成了很多死元组。我已经将全局配置更新为以下内容:
autovacuum_vacuum_scale_factor = 0
autovacuum_vacuum_threshold = 10000
autovacuum_vacuum_cost_limit = 2000
autovacuum_max_workers = 6
使用这些设置,我的意图是,只要死元组增加到10,000条记录以上,自动清理就会清理掉它。
但是,我发现当表忙于其他插入等操作时,无效元组计数不会改变。它固定在某个死元组计数上。只有当db活动在晚上变慢时,自动清理才可以正常工作。
我需要全天候自动处理死元组。我将如何完成?我需要增加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
答案 0 :(得分:1)
死元组不是您的问题。
您真正的问题在其他地方;我在下面突出显示了它。
慢速查询中的顺序扫描:
Buffers: shared hit=3015376 read=16753169 dirtied=1759802 written=1360458
快速查询中的顺序扫描:
Buffers: shared hit=15812 read=19753809
大约有200万个表块包含最近被写入或更新的元组。
在编写元组时,PostgreSQL尚不知道事务将提交还是回退,因此该信息未存储在元组中。但是,它记录在提交日志中,该日志存储在pg_xact
(或pg_clog
中,具体取决于您的版本)中。
现在,第一个出现并读取新编写的元组的阅读器将必须查阅提交日志,以查明元组是否“存在”。为了节省以后的读者的麻烦,它在元组中设置了所谓的提示位以反映该信息。
这会发生变化,因此“弄脏”了包含元组的块,并且如果您设置的shared_buffers
很小,并且用尽了可用的缓冲区,则后端甚至必须将块写出到存储中以清理它们并腾出空间。
这就是让您的查询如此缓慢的原因。
对表进行抽真空使问题消失了,因为VACUUM
不仅清理了死元组,而且还为您设置了提示位(它也是一个读取器!)。
要验证这一点,请第二次运行相同的SELECT
而无需清理表,您会发现300万个死元组的运行速度与之相同,因为现在提示位都已设置。
这就是为什么在装载很多行后即使在表上没有任何清理的情况下在表上运行VACUUM
也是一个好主意的原因-您可以为第一个阅读器节省很多工作。
想法:增加shared_buffers
会改善这种情况吗?
但是由于清理表可以解决问题,因此您也可以使用autovacuum来更频繁地设置提示位。
为此,您可以将autovacuum_vacuum_scale_factor
设置为0,并将autovacuum_vacuum_threshold
设置为较大的常量( way 大于10000),这样就不会有太多行提示位。
此外,将autovacuum_vacuum_cost_delay
设置为0,以便快速完成自动真空操作。
请勿全局更改这些参数,请使用ALTER TABLE ... SET (...)
仅为此表设置它们。