9.3 9.4 9.5 9.6 10 11 12 13 14 15 16 17 Current(18)
PostgreSQL中文社区 问题报告 纠错本页面

24.1. 日常清理 #

24.1.1. 清理的基础知识
24.1.2. 恢复磁盘空间
24.1.3. 更新规划器统计信息
24.1.4. 更新可见性映射
24.1.5. 防止事务 ID 回绕失败
24.1.6. 自动清理后台进程

PostgreSQL数据库要求周期性的清理维护。对于很多安装,让自动清理守护进程来执行清理已经足够,如第 24.1.6 节所述。你可能需要调整其中描述的自动清理参数来获得最佳结果。某些数据库管理员会希望使用手动管理的VACUUM命令来对守护进程的活动进行补充或者替换,这通常使用cron任务计划程序脚本来执行。要正确地设置手动管理的清理,最重要的是理解接下来几小节中讨论的问题。依赖自动清理的管理员最好也能略读该内容以帮助他们理解和调整自动清理。

24.1.1. 清理的基础知识 #

PostgreSQLVACUUM命令出于几个原因必须定期处理每一个表:

  1. 恢复或重用被已更新或已删除行所占用的磁盘空间。
  2. 更新被PostgreSQL查询规划器使用的数据统计信息。
  3. 更新可见性映射,它可以加速只用索引的扫描。
  4. 保护老旧数据不会由于事务 ID 回卷多事务 ID 回卷而丢失。

正如后续小节中解释的,每一个原因都将指示以不同的频率和范围执行VACUUM操作。

有两种VACUUM的变体:标准VACUUMVACUUM FULLVACUUM FULL可以收回更多磁盘空间但是运行起来更慢。另外,标准形式的VACUUM可以和生产数据库操作并行运行(SELECTINSERTUPDATEDELETE等命令将继续正常工作,但在清理期间你无法使用ALTER TABLE等命令来更新表的定义)。VACUUM FULL要求在其工作的表上得到一个ACCESS EXCLUSIVE锁,因此无法和对此表的其他使用并行。因此,通常管理员应该努力使用标准VACUUM并且避免VACUUM FULL

VACUUM会产生大量I/O流量,这将导致其他活动会话性能变差。可以调整一些配置参数来减少后台清理活动造成的性能冲击 — 参阅第 19.10.2 节

24.1.2. 恢复磁盘空间 #

PostgreSQL中,一次行的UPDATEDELETE不会立即移除该行的旧版本。这种方法对于从多版本并发控制(MVCC,见第 13 章)获益是必需的:当旧版本仍可能对其他事务可见时,它不能被删除。但是最后,任何事务都不会再对一个过时的或者被删除的行版本感兴趣。它所占用的空间必须被回收来用于新行,这样可以避免磁盘空间需求的无限制增长。这通过运行VACUUM完成。

VACUUM的标准形式移除表和索引中的死亡行版本并将该空间标记为可在未来重用。不过,它将不会把该空间交还给操作系统,除非在特殊的情况中表尾部的一个或多个页面变成完全空闲并且能够很容易地得到一个排他表锁。相反,VACUUM FULL通过把死亡空间之外的内容写成一个完整的新版本表文件来主动紧缩表。这将最小化表的尺寸,但是要花较长的时间。它也需要额外的磁盘空间用于表的新副本,直到操作完成。

例行清理的一般目标是多做标准的VACUUM来避免需要VACUUM FULL。自动清理守护进程尝试这样工作,并且实际上永远不会发出VACUUM FULL。在这种方法中,其思想不是让表保持它们的最小尺寸,而是保持磁盘空间使用的稳定状态:每个表占用的空间等于其最小尺寸外加清理之间将使用的空间量。尽管VACUUM FULL可被用来把一个表收缩回它的最小尺寸并将该磁盘空间交还给操作系统,但是如果该表将在未来再次增长这样就没什么意义。因此,对于维护频繁被更新的表,适度运行标准VACUUM比少量运行VACUUM FULL要更好。

一些管理员更喜欢自己计划清理,例如在晚上负载低时做所有的工作。根据一个固定日程来做清理的难点在于,如果一个表有一次预期之外的更新活动尖峰,它可能膨胀得真正需要VACUUM FULL来回收空间。使用自动清理守护进程可以减轻这个问题,因为守护进程会根据更新活动动态规划清理操作。除非你的负载是完全可以预估的,完全禁用守护进程是不理智的。一种可能的折中方案是设置守护进程的参数,这样它将只对异常的大量更新活动做出反应,因而保证事情不会失控,而在负载正常时采用有计划的VACUUM来做批量工作。

对于那些不使用自动清理的用户,一种典型的方法是计划一个数据库范围的VACUUM,该操作每天在低使用量时段执行一次,并根据需要辅以在重度更新表上的更频繁的清理(一些有着极高更新率的安装会每几分钟清理一次它们的最繁忙的表)。如果你在一个集簇中有多个数据库,别忘记VACUUM每一个,你会用得上vacuumdb程序。

提示

当一个表因为大量更新或删除活动而包含大量死亡行版本时,纯粹的VACUUM可能不能令人满意。如果你有这样一个表并且你需要回收它占用的过量磁盘空间,你将需要使用VACUUM FULL,或者CLUSTER,或者ALTER TABLE的表重写变体之一。这些命令重写该表的一整个新拷贝并且为它构建新索引。所有这些选项都要求ACCESS EXCLUSIVE 锁。注意它们也临时使用大约等于该表尺寸的额外磁盘空间,因为直到新表和索引完成之前旧表和索引都不能被释放。

提示

如果你有一个表,它的整个内容会被周期性删除,考虑用TRUNCATE而不是先用DELETE再用VACUUMTRUNCATE会立刻移除该表的整个内容,而不需要一次后续的VACUUMVACUUM FULL来回收现在未被使用的磁盘空间。其缺点是会违背严格的MVCC语义。

24.1.3. 更新规划器统计信息 #

PostgreSQL查询规划器依赖于有关表内容的统计信息来为查询产生好的计划。这些统计信息由ANALYZE命令收集,它除了直接被调用之外还可以作为VACUUM的一个可选步骤被调用。拥有适度准确的统计信息很重要,否则差的计划可能降低数据库性能。

自动清理守护进程如果被启用,当一个表的内容被改变得足够多时,它将自动发出ANALYZE命令。不过,管理员可能更喜欢依靠手动的ANALYZE操作,特别是如果知道一个表上的更新活动将不会影响感兴趣的列的统计信息时。守护进程严格地按照一个被插入或更新行数的函数来计划ANALYZE,它不知道那是否将导致有意义的统计信息改变。

在分区和继承子项中更改的元组不会触发父表上的分析。如果父表为空或很少更改, 它可能永远不会被自动清理处理,并且整个继承树的统计信息将不会被收集。 为了保持统计信息最新,需要手动在父表上运行ANALYZE

正如用于空间恢复的清理一样,频繁更新统计信息对重度更新的表更加有用。但即使对于一个重度更新的表,如果该数据的统计分布没有很大改变,也没有必要更新统计信息。一个简单的经验法则是考虑表中列的最大和最小值改变了多少。例如,一个包含行被更新时间的timestamp列将在行被增加和更新时有一直增加的最大值;这样一列将可能需要更频繁的统计更新,而一个包含一个网站上被访问页面URL的列则不需要。URL列可以经常被更改,但是其值的统计分布的变化相对很慢。

可以在指定表上运行ANALYZE甚至在表的指定列上运行,因此如果你的应用需要,可以更加频繁地更新某些统计。但实际上,通常只分析整个数据库是最好的,因为它是一种很快的操作。ANALYZE对一个表的行使用一种统计的随机采样,而不是读取每一个单一行。

提示

尽管对每列的ANALYZE频度调整可能不是非常富有成效,你可能会发现值得为每列调整被ANALYZE收集统计信息的详细程度。经常在WHERE中被用到的列以及数据分布非常不规则的列可能需要比其他列更细粒度的数据直方图。见ALTER TABLE SET STATISTICS,或者使用default_statistics_target配置参数改变数据库范围的默认值。

还有,默认情况下关于函数的选择度的可用信息是有限的。但是,如果你创建一个统计对象或者使用函数的表达式索引,关于该函数的有用的统计信息将被收集,这些信息能够大大提高使用该表达式索引的查询计划的质量。

提示

自动清理守护进程不会为外部表发出ANALYZE命令,因为无法确定一个合适的频度。如果你的查询需要外部表的统计信息来正确地进行规划,比较好的方式是按照一个合适的时间表在那些表上手工运行ANALYZE命令。

提示

自动清理守护进程不会为分区表发出ANALYZE命令。只有在父表本身发生更改时,继承父表才会被分析 - 子表的更改不会触发父表的自动分析。如果你的查询需要父表的统计信息以进行正确的规划,则有必要定期对这些表运行手动ANALYZE以保持统计信息最新。

24.1.4. 更新可见性映射 #

清理机制为每一个表维护着一个可见性映射,它被用来跟踪哪些页面只包含对所有活动事务(以及所有未来的事务,直到该页面被再次修改)可见的元组。这样做有两个目的。第一,清理本身可以在下一次运行时跳过这样的页面,因为其中没有什么需要被清除。

第二,这允许PostgreSQL回答一些只用索引的查询,而不需要引用底层表。因为PostgreSQL的索引不包含元组的可见性信息,一次普通的索引扫描会为每一个匹配的索引项获取堆元组,用来检查它是否能被当前事务所见。另一方面,一次只用索引的扫描会首先检查可见性映射。如果它了解到在该页面上的所有元组都是可见的,堆获取就可以被跳过。这对大数据集很有用,因为可见性映射可以防止磁盘访问。可见性映射比堆小很多,因此即使堆非常大,可见性映射也可以很容易地被缓存起来。

24.1.5. 防止事务 ID 回绕失败 #

PostgreSQL的 MVCC 事务语义依赖于能够比较事务 ID(XID)数字:如果一个行版本的插入 XID 大于当前事务的 XID,它就是属于未来的并且不应该对当前事务可见。但是因为事务 ID 的尺寸有限(32位),一个长时间(超过 40 亿个事务)运行的集簇会遭受到事务 ID 回绕问题:XID 计数器回绕到 0,并且本来属于过去的事务突然间就变成了属于未来 — 这意味着它们的输出变成不可见。简而言之,灾难性的数据丢失(实际上数据仍然在那里,但是如果你不能得到它也无济于事)。为了避免发生这种情况,有必要至少每 20 亿个事务就清理每个数据库中的每个表。

周期性的清理能够解决该问题的原因是,VACUUM会把行标记为 冻结,这表示它们是被一个在足够远的过去提交的事务所插入, 这样从 MVCC 的角度来看,效果就是该插入事务对所有当前和未来事务来说当然都 是可见的。普通 XID 使用模-232算 法来比较。这意味着对于每一个普通 XID都有 20 亿个 XID 更老并且 有 20 亿个更新,另一种解释的方法是普通 XID 空间是没有端点的环。 因此,一旦一个行版本创建时被分配了一个特定的普通 XID,该行版本将成为接下 来 20 亿个事务的过去(与我们谈论的具体哪个普通 XID 无关)。如 果在 20 亿个事务之后该行版本仍然存在,它将突然变得好像在未来。要阻止这一切 发生,被冻结行版本会被看成其插入 XID 为FrozenTransactionId, 这样它们对所有普通事务来说都是在过去,而不管回绕问题。并且这样 的行版本将一直有效直到被删除,不管它有多旧。

注意

在9.4之前的PostgreSQL版本中,实际上会通过将一行的插入 XID 替换为 FrozenTransactionId来实现冻结,这种FrozenTransactionId在行的 xmin系统列中是可见的。较新的版本只是设置一个标志位, 保留行的原始xmin用于可能发生的鉴别用途。不过, 在9.4之前版本的数据库pg_upgrade中可能仍会找到 xmin等于FrozenTransactionId (2)的行。

此外,系统目录可能会包含xmin等于BootstrapTransactionId (1) 的行,这表示它们是在initdb的第一个阶段被插入的。 和FrozenTransactionId相似,这个特殊的 XID 被认为比所有正常 XID 的年龄都要老。

vacuum_freeze_min_age控制在其行版本被冻结前一个 XID 值应该有多老。如果被冻结的行将很快会被再次修改,增加这个设置可以避免不必要 的工作。但是减少这个设置会增加在表必须再次被清理之前能够流逝的事务数。

VACUUM 使用 可见性映射 来确定必须扫描的表页。通常,它会跳过没有任何死行版本的页,即使这些页 可能仍然有旧 XID 值的行版本。因此,正常的 VACUUM 不会 总是冻结表中的每个旧行版本。当这种情况发生时,VACUUM 最终需要执行一次 激进的清理,这将冻结所有符合条件的 未冻结 XID 和 MXID 值,包括来自所有可见但未完全冻结的页的值。

如果一个表积累了所有可见但未完全冻结的页的积压,正常的清理可能会选择 扫描可跳过的页以努力冻结它们。这样做可以减少下一个激进清理必须扫描的页数。 这些被称为 急切扫描 页。可以通过增加 vacuum_max_eager_freeze_failure_rate 来调整急切扫描,以尝试冻结 更多的所有可见页。即使急切扫描将所有可见但未完全冻结的页数保持在最低限度, 大多数表仍然需要定期进行激进清理。然而,在激进清理期间,任何成功急切冻结的 页可能会被跳过,因此急切冻结可能会最小化激进清理的开销。

vacuum_freeze_table_age 控制何时对表进行激进清理。如果自上次扫描以来经过的事务数量大于 vacuum_freeze_table_age 减去 vacuum_freeze_min_age, 则会扫描所有可见但未完全冻结的页。将 vacuum_freeze_table_age 设置为 0 会强制 VACUUM 始终使用其激进策略。

一个表能保持不被清理的最长时间是 20 亿个事务减去 vacuum_freeze_min_age 值 在上次激进清理时的值。如果它超过该时间没有被清理,可能会导致数据丢失。要保证这 不会发生,将在任何包含比 autovacuum_freeze_max_age 配置参数所指定的 年龄更老的 XID 的未冻结行的表上调用自动清理(即使自动清理被禁用也会发生)。

这意味着如果一个表没有被清理,大约每 autovacuum_freeze_max_age 减去 vacuum_freeze_min_age 事务就会在该表上调用一次自动清理。对那些为了 空间回收目的而被正常清理的表,这是无关紧要的。然而,对静态表(包括接收插入但没有 更新或删除的表)就没有为空间回收而清理的需要,因此尝试在非常大的静态表上强制 自动清理的间隔最大化会非常有用。显然我们可以通过增加 autovacuum_freeze_max_age 或减少 vacuum_freeze_min_age 来实现此目的。

vacuum_freeze_table_age 的实际最大值是 0.95 * autovacuum_freeze_max_age, 高于它的设置将被上限到最大值。一个高于 autovacuum_freeze_max_age 的值没有意义, 因为不管怎样在那个点上都会触发一次防回卷自动清理,并且 0.95 的乘数为在防回卷自动清理 发生之前运行一次手动 VACUUM 留出了一些空间。作为一种经验法则, vacuum_freeze_table_age 应当被设置成一个低于 autovacuum_freeze_max_age 的值,留出一个足够的空间让一次被正常调度的 VACUUM 或一次被正常删除和更新活动 触发的自动清理可以在这个窗口中被运行。将它设置得太接近可能导致防回卷自动清理, 即使该表最近因为回收空间的目的被清理过,而较低的值将导致更频繁的激进清理。

增加 autovacuum_freeze_max_age(以及和它一起的 vacuum_freeze_table_age) 的唯一不足是数据库集簇的 pg_xactpg_commit_ts 子目录将占据更多空间, 因为它必须存储所有向后 autovacuum_freeze_max_age 范围内的所有事务的提交状态和(如果启用了 track_commit_timestamp)时间戳。提交状态为每个事务使用两个二进制位,因此如果 autovacuum_freeze_max_age 被设置为它的最大允许值 20 亿, pg_xact 将会增长到 大约 0.5 吉字节, pg_commit_ts 大约 20GB。如果这对于你的总数据库尺寸是微小的, 我们推荐设置 autovacuum_freeze_max_age 为它的最大允许值。否则,基于你想要允许 pg_xactpg_commit_ts 使用的存储空间大小来设置它(默认情况下 2 亿个事务大约等于 pg_xact 的 50 MB 存储空间, pg_commit_ts 的 2GB 的存储空间)。

减小 vacuum_freeze_min_age 的一个不足之处是它可能导致 VACUUM 做无用的工作: 如果该行在被替换成 FrozenXID 之后很快就被修改(导致该行获得一个新的 XID), 那么冻结一个行版本就是浪费时间。因此该设置应该足够大,这样直到行不再可能被修改之前, 它们都不会被冻结。

要跟踪数据库中最老的未冻结XID的年龄,VACUUM将XID统计信息存储在系统表 pg_classpg_database中。特别地, 表的pg_class行的relfrozenxid列包含 在最近一次成功推进relfrozenxid(通常是最近的主动VACUUM)结束时 的最老未冻结XID。同样,数据库的pg_database行的 datfrozenxid列是该数据库中出现的未冻结XIDs的下界 —— 它只是 数据库中每个表的relfrozenxid值的最小值。查看这些信息的一种方便 的方法是执行如下查询:

SELECT c.oid::regclass as table_name,
       greatest(age(c.relfrozenxid),age(t.relfrozenxid)) as age
FROM pg_class c
LEFT JOIN pg_class t ON c.reltoastrelid = t.oid
WHERE c.relkind IN ('r', 'm');

SELECT datname, age(datfrozenxid) FROM pg_database;

age列测量从截止XID到当前事务XID的事务数量。

提示

当指定VACUUM命令的VERBOSE参数时, VACUUM会打印关于表的各种统计信息。这包括 relfrozenxidrelminmxid的更新信息,以及新冻结页面的数量。 当自动清理日志记录(由log_autovacuum_min_duration控制)报告 自动清理执行的VACUUM操作时,相同的详细信息也会出现在 服务器日志中。

虽然 VACUUM 主要扫描自上次清理以来已修改的页,但它也可能 急切扫描一些所有可见但未完全冻结的页以尝试冻结它们,但只有在扫描了 可能包含未冻结 XIDs 的表的每个页时,relfrozenxid 才会 被推进。这发生在 relfrozenxid 超过 vacuum_freeze_table_age 事务时,使用 VACUUMFREEZE 选项时,或者当所有未完全冻结的页恰好需要清理以 删除死行版本时。当 VACUUM 扫描表中每个未完全冻结的页时, 应将 age(relfrozenxid) 设置为比使用的 vacuum_freeze_min_age 设置稍微大一点的值(多出自 VACUUM 开始以来启动的事务数量)。VACUUMrelfrozenxid 设置为表中剩余的最旧 XID,因此最终值可能比严格要求的值要新得多。 如果在达到 autovacuum_freeze_max_age 之前没有对表发出 任何推进 relfrozenxidVACUUM, 则将很快强制对该表进行自动清理。

如果由于某种原因自动清理无法清除表中的旧XID,当数据库中最旧的XID距离环绕点达到四千万 事务时,系统将开始发出如下警告信息:

WARNING:  database "mydb" must be vacuumed within 39985967 transactions
HINT:  To avoid XID assignment failures, execute a database-wide VACUUM in that database.

(A manual VACUUM should fix the problem, as suggested by the hint; but note that the VACUUM should be performed by a superuser, else it will fail to process system catalogs, which prevent it from being able to advance the database's datfrozenxid.) 如果忽略这些警告,系统将在剩余事务少于三百万且即将回绕时拒绝分配新的XID:

ERROR:  database is not accepting commands that assign new XIDs to avoid wraparound data loss in database "mydb"
HINT:  Execute a database-wide VACUUM in that database.

在这种情况下,任何已经进行中的事务都可以继续,但只能启动只读事务。修改数据库记录或截断关系的操作将失败。 VACUUM命令仍然可以正常运行。请注意,与早期版本中有时推荐的做法相反, 不需要也不应停止postmaster或进入单用户模式来恢复正常操作。 相反,请按照以下步骤操作:

  1. 解决旧的预处理事务。您可以通过检查 pg_prepared_xactsage(transactionid) 较大的行来找到这些事务。此类事务应当 被提交或回滚。
  2. 结束长时间运行的未完成事务。您可以通过检查 pg_stat_activity 中的行来找到这些事务, 其中 age(backend_xid)age(backend_xmin) 值较大。 这些事务应当被提交或回滚,或者可以使用 pg_terminate_backend 终止会话。
  3. 删除任何旧的复制槽。使用 pg_stat_replication来 查找 age(xmin)age(catalog_xmin) 较大的槽。 在许多情况下,这些槽是为已不存在或长时间宕机的服务器复制而创建的。 如果删除了仍然存在且可能仍尝试连接该槽的服务器的槽,则该副本可能需要重建。
  4. 在目标数据库中执行 VACUUM。对整个数据库执行 VACUUM 是最简单的; 为了减少所需时间,也可以在 relminxid 最旧的表上手动发出 VACUUM 命令。 在这种情况下不要使用 VACUUM FULL,因为它需要一个 XID,因此会失败,除非在超级用户模式下, 此时它会消耗一个 XID,从而增加事务 ID 回绕的风险。也不要使用 VACUUM FREEZE, 因为它会执行超过恢复正常操作所需的最小工作量。
  5. 一旦恢复正常操作,确保目标数据库中自动清理功能配置正确,以避免将来出现问题。

注意

在早期版本中,有时需要停止 postmaster 并以单用户模式 VACUUM 数据库。 在典型情况下,这不再是必要的,应尽量避免,因为这会导致系统停机。 这也更加危险,因为它会禁用旨在防止数据丢失的事务 ID 环绕保护措施。 在这种情况下使用单用户模式的唯一原因是,如果您希望 TRUNCATEDROP 不需要的表以避免需要 VACUUM 它们。 三百万事务的安全边界存在是为了让管理员执行此操作。有关使用单用户模式的详细信息,请参阅 postgres 参考页面。

24.1.5.1. 多事务和环绕 #

多事务 ID 用于支持多个事务的行锁定。由于元组头中 存储锁信息的空间有限,因此当有多个事务同时锁定一行时,该信息会被编码为 多事务 ID,简称为多事务 ID。有关哪些事务 ID 包含在特定多事务 ID 中的信息 单独存储在 pg_multixact 子目录中,只有多事务 ID 出现在 元组头的 xmax 字段中。与事务 ID 类似,多事务 ID 实现为 32 位计数器及相应的存储,这一切都需要仔细的老化管理、存储清理和环绕处理。 还有一个单独的存储区域,用于保存每个多事务的成员列表,该列表也使用 32 位计数器, 也必须进行管理。系统函数 pg_get_multixact_members() 可以用来检查 与多事务 ID 相关的事务 ID。

在一次VACUUM表扫描(部分或者全部)期间,任何比 vacuum_multixact_freeze_min_age 要老的多事务 ID 会被替换为一个不同的值,该值可以是零值、 一个单一事务 ID 或者一个更新的多事务 ID。 对于每一个表,pg_class.relminmxid 存储了在该表任意元组中仍然存在的最老可能多事务 ID。如果这个值比 vacuum_multixact_freeze_table_age老, 将强制一次全表扫描。可以在 pg_class.relminmxid 上使用mxid_age()来找到它的年龄。

无论是什么原因导致的激进的VACUUM,都保证能够推进表的relminmxid。 最终,当所有数据库中的所有表都被扫描并且它们最老的多事务值被推进时,旧的多事务的磁盘存储可以被移除。

作为一种安全机制,任何 multixact-age 大于 autovacuum_multixact_freeze_max_age 的表都会进行激进的清理扫描。 此外,如果 multixacts 成员占用的存储空间超过约 10GB,所有表的激进清理扫描 频率都会增加,优先从 multixact-age 最老的表开始。即使 autovacuum 名义上被禁用, 这两种激进扫描仍然会发生。成员存储区可以增长到约 20GB 才会达到环绕限制。

类似于 XID 的情况,如果自动清理无法清除表中的旧 MXID,当数据库中最旧的 MXID 距离回绕点达到四千万事务时,系统将开始发出警告信息。并且,就像 XID 的情况 一样,如果忽略这些警告,当距离回绕点不足三百万时,系统将拒绝生成新的 MXID。

当 MXIDs 用尽时,可以通过与 XIDs 用尽时相同的方式恢复正常操作。按照前一节中的相同步骤进行, 但有以下区别:

  1. 如果没有可能出现在多事务中的事务和准备事务,则可以忽略运行事务和准备事务。
  2. MXID 信息在诸如 pg_stat_activity 之类的系统视图中不直接可见; 但是,查找旧的 XIDs 仍然是确定哪些事务导致 MXID 环绕问题的好方法。
  3. XID 用尽将阻止所有写事务,但 MXID 用尽只会阻止一部分写事务,具体来说,那些涉及 需要 MXID 的行锁的写事务。

24.1.6. 自动清理后台进程 #

PostgreSQL有一个可选的但被高度推荐的特性autovacuum,它的目的是自动执行VACUUMANALYZE命令。当它被启用时,自动清理会检查被大量插入、更新或删除元组的表。这些检查会利用统计信息收集功能,因此除非track_counts被设置为true,自动清理就不可以被使用。在默认配置下,自动清理是被启用的并且相关配置参数已被正确配置。

自动清理后台进程实际上由多个进程组成。有一个称为 自动清理启动器的常驻后台进程, 它负责为所有数据库启动自动清理工作者进程。 启动器将把工作散布在一段时间上,它每隔 autovacuum_naptime秒尝试在每个数据库中启动一个工作者 (因此,如果安装中有N个数据库,则每 autovacuum_naptime/N秒将启动一个新的工作者)。 在同一时间只允许最多autovacuum_max_workers 个工作者进程运行。如果有超过autovacuum_max_workers 个数据库需要被处理,下一个数据库将在第一个工作者结束后马上被处理。 每一个工作者进程将检查其数据库中的每一个表并且在需要时执行 VACUUM和/或ANALYZE。 可以设置log_autovacuum_min_duration 来监控自动清理工作者的活动。

如果在一小段时间内多个大型表都变得可以被清理,所有的自动清理工作者可能都会被占用来在一段长的时间内清理这些表。这将会造成其他的表和数据库无法被清理,直到一个工作者变得可用。对于一个数据库中的工作者数量并没有限制,但是工作者确实会试图避免重复已经被其他工作者完成的工作。注意运行着的工作者的数量不会被计入max_connectionssuperuser_reserved_connections限制。

如果表的 relfrozenxid 值超过 autovacuum_freeze_max_age 事务, 则始终会对其进行清理(这也适用于通过存储参数修改的那些表的冻结最大年龄; 见下文)。否则,如果自上次 VACUUM 以来废弃的元组数量超过 清理阈值,则会对该表进行清理。清理阈值定义为:

清理阈值 = 最小值(清理最大阈值, 清理基础阈值 + 清理比例因子 * 元组数量)

其中清理最大阈值为 autovacuum_vacuum_max_threshold, 清理基础阈值为 autovacuum_vacuum_threshold, 清理比例因子为 autovacuum_vacuum_scale_factor, 元组数量为 pg_class.reltuples

如果自上次清理以来插入的元组数量超过定义的插入阈值,则该表也会被清理, 插入阈值定义为:

插入清理阈值 = 清理基础插入阈值 + 插入清理比例因子 * 元组数量 * 表中未冻结的百分比

其中插入清理基础阈值为 autovacuum_vacuum_insert_threshold, 插入清理比例因子为 autovacuum_vacuum_insert_scale_factor, 元组数量为 pg_class.reltuples, 表中未冻结的百分比为 1 - pg_class.relallfrozen / pg_class.relpages。 这样的清理可能允许表的部分被标记为 全部可见,并且还允许元组被冻结, 这可以减少后续清理所需的工作。对于接收 INSERT 操作但几乎没有 UPDATE/DELETE 操作的表,降低表的 autovacuum_freeze_min_age 可能是有益的,因为这可能允许 元组在早期清理中被冻结。废弃元组的数量和插入元组的数量是从累积统计系统中获得的; 这是一个最终一致的计数,由每个 UPDATEDELETEINSERT 操作更新。如果表的 relfrozenxid 值 超过 vacuum_freeze_table_age 事务,则会执行激进清理以冻结旧元组并推进 relfrozenxid

对于分析,也使用了一个相似的阈值:

分析阈值 = 分析基本阈值 + 分析缩放系数 * 元组数

该阈值将与自从上次ANALYZE以来被插入、更新或删除的元组数进行比较。

分区表不直接存储元组,因此不会被自动清理处理。(自动清理会像处理其他表一样处理表分区。)不幸的是,这意味着自动清理不会在分区表上运行ANALYZE,这可能导致引用分区表统计信息的查询产生次优化的计划。当分区表首次填充时,您可以通过手动运行ANALYZE来解决这个问题,并在它们的分区数据分布发生显著变化时再次运行。

临时表不能被自动清理访问。因此,适当的清理和分析操作必须通过 会话期间的SQL命令来执行。

默认的阈值和缩放系数都取自于postgresql.conf,但是可以为每一个表重写它们(和许多其他自动清理控制参数);详情参见Storage Parameters。 如果一个设置已经通过一个表的存储参数修改,那么在处理该表时使用该值,否则使用全局设置。 全局设置请参阅第 19.10.1 节以获取更多详细信息。

当多个工作者运行时,在所有运行着的工作者之间自动清理代价延迟参数 (参阅第 19.10.2 节)是 平衡的,这样不管实际运行的工作者数量是多少, 对于系统的总体 I/O 影响总是相同的。不过,任何正在处理已经设置了每表 autovacuum_vacuum_cost_delayautovacuum_vacuum_cost_limit 存储参数的表的工作者不会被考虑在均衡算法中。

autovacuum工作进程通常不会阻止其他命令。如果某个进程尝试获取与autovacuum持有的SHARE UPDATE EXCLUSIVE锁冲突的锁,则锁获取将中断该autovacuum。有关冲突的锁定模式,请参见表 13.2。但是,如果autovacuum正在运行以防止事务ID回卷(即在pg_stat_activity视图中的autovacuum查询名以(to prevent wraparound)结尾),则autovacuum不会被自动中断。

警告

定期运行需要获取与SHARE UPDATE EXCLUSIVE锁冲突的锁的命令(例如ANALYZE)可能会让autovacuum始终无法完成。