小表会导致性能极度下降,已通过强制 VACUUM 修复。为什么?

Jul*_*ien 6 postgresql performance statistics autovacuum postgresql-9.6 query-performance

我使用 PostgreSQL 9.6。

我有一个连接 17 个表的查询,其中 9 个有几百万行。查询运行良好,但本周其性能迅速下降。EXPLAIN 的输出没有帮助(所有扫描都是索引扫描,除了非常小的表),我不得不尝试从查询中删除表以隔离导致降级的表。

事实证明,一个包含 40 行的不起眼的表破坏了查询:800 ms 没有该表,而有 30 s。我在桌子上运行了 VACUUM FULL,它运行了大约一秒钟,现在性能恢复正常。

我的问题:

  1. 什么可以解释 <10kb 的表像这样破坏性能?
  2. 以后如何避免同样的问题?

在调试过程中,我对另一台服务器进行了基本备份,因此我有两个文件系统级别的数据库副本,其中一个我没有运行 VACUUM FULL。当我使用 pgAdmin 登录到 unvacuumed 副本时,我收到以下消息:

表“public.clients”上的估计行数与实际行数显着不同。您应该在此表上运行 VACUUM ANALYZE。

unvacuumed 表有 40 行计数和 0 估计。以下是屏幕截图中的其余统计数据。

pgAdmin 统计信息

Erw*_*ter 6

该表可能很小,但只要 Postgres 预计大约为0行,它就有可能选择与大约40行不同的查询计划- 相同的查询计划效率不高。

由于连接乘以结果行而不是仅仅添加到它们中,因此当连接到像您的示例中那样有几百万行的大表时,小表中的 40 行会产生巨大的影响。这种差异可以很容易地解释执行时间的 30 倍。
或者正如手册所说

拥有合理准确的统计数据很重要,否则计划的错误选择可能会降低数据库性能。

默认autovacuum设置适用于大多数安装。考虑:

但是对于包含数百万行的多个表的数据库,我会考虑ANALYZE不时调整所选表的每个表设置和整个数据库的手册。

剩下的问题

一季度。为什么 autovacuum 没有ANALYZE自动启动?
Q2。为什么VACUUM FULL解决了问题?

Q2很简单:虽然其他重要的统计数据只更新了ANALYZE,但 中的基本计数估计pg_class.reltuples更新得更频繁。手册:

表中的行数。这只是规划器使用的估计值。它由VACUUMANALYZE和一些 DDL 命令更新,例如CREATE INDEX.

Q1更复杂。

再次手册

守护进程ANALYZE严格按照插入或更新的行数进行调度;它不知道这是否会导致有意义的统计变化。

相关设置(除其他外):

autovacuum_analyze_threshold( integer)

指定触发ANALYZE任何一张表中所需的插入、更新或删除元组的最小数量。默认值为50 个 元组。该参数只能在postgresql.conf文件中或服务器命令行中设置;但是可以通过更改表存储参数来覆盖单个表的设置。

autovacuum_analyze_scale_factor( floating point)

指定 autovacuum_analyze_threshold在决定是否触发 ANALYZE. 默认值为0.1(表大小的 10%)。该参数只能在 postgresql.conf 文件或服务器命令行中设置;但是可以通过更改表存储参数来覆盖单个表的设置。

大胆强调我的。

演示

确保测试数据库大部分处于空闲状态以避免测试工件,并且您正在使用默认设置运行:

SELECT * FROM pg_settings WHERE name ~ '^autovacuum|track_counts';
Run Code Online (Sandbox Code Playgroud)

最重要的是:

autovacuum_analyze_scale_factor = 0.1
autovacuum_analyze_threshold = 50
autovacuum_naptime = 60
track_counts = on
Run Code Online (Sandbox Code Playgroud)

基本上,autovacuum 每分钟检查一次是否有任何表具有last_estimate / 100 + 50 行更改并ANALYZE为这些行启动。

要了解您的情况发生了什么:

CREATE TABLE t50 (id int primary key, foo text);
INSERT INTO t50 SELECT g, 'txt' || g FROM generate_series(1,50) g;
SELECT reltuples FROM pg_class WHERE oid = 't50'::regclass;
Run Code Online (Sandbox Code Playgroud)

pg_class.reltuples是表的估计行数。更多在这里:

你会得到0。等待 2 分钟以确保我们跨越了 1 分钟的延迟。再检查一遍。还是0。现在再插入一行并再次检查:

INSERT INTO t50 VALUES (51, 'txt51 triggers analyze');
SELECT reltuples FROM pg_class WHERE oid = 't50'::regclass;
Run Code Online (Sandbox Code Playgroud)

还是0。再等 2 分钟,再检查一次。多田!我们看到 的更新计数51。Autovacuum 直到 51 行被插入(或更新或删除)才开始。

要查看更多详细信息(包括 的时间戳last_autoanalyze):

SELECT * FROM pg_stat_all_tables WHERE relid = 't50'::regclass;
Run Code Online (Sandbox Code Playgroud)

有关的:

解决方案

运行ANALYZEpublic.clients手动一次(或整个数据库,它的价格便宜),并为这个重要的表用更积极的每个表的自动清理设置。喜欢:

ALTER TABLE public.clients SET (autovacuum_analyze_scale_factor = 0.01
                              , autovacuum_analyze_threshold = 10);
Run Code Online (Sandbox Code Playgroud)

由于其他原因,您可能还想审核某些大表的设置。相比:

同样重要

要加入17桌,这远远超出的默认设置join_collapse_limit8。您可能希望使用显式连接语法(也许您已经这样做了)并重写您的查询以将最具选择性的表(或具有最具选择性的谓词)放在SELECT列表中的首位。有关的:


PS:我想我在运行上面的测试时发现了一个小文档错误。手册上autovacuum_analyze_threshold写着:

指定触发任意一张表中所需的插入、更新或删除元组的最小数量ANALYZE

这表明50 个插入 trigger ANALYZE,而不是像我观察到的51 个。类似pg_settings.short_desc

分析之前元组插入、更新或删除的最小数量。

事实上,这里手册中对 autovacuum 的解释符合我的观察:

否则,如果自上次以来废弃的元组数量VACUUM 超过“真空阈值”,则该表被真空吸尘。

前两句似乎有点不正确。
我提交了一个错误报告。