2024年9月26日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不支持的版本:11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 7.3 / 7.2

24.1. 常规 Vacuum 操作 #

PostgreSQL 数据库需要定期维护,称为Vacuum 操作。对于许多安装来说,让自动 Vacuum 守护进程执行 Vacuum 操作就足够了,该守护进程在第 24.1.6 节中进行了描述。您可能需要调整此处描述的自动 Vacuum 参数以获得适合您情况的最佳结果。某些数据库管理员希望用手动管理的VACUUM命令来补充或替换守护进程的活动,这些命令通常由cronTask Scheduler脚本根据计划执行。要正确设置手动管理的 Vacuum 操作,必须了解以下几个小节中讨论的问题。依赖于自动 Vacuum 的管理员可能仍希望浏览此内容,以帮助他们理解和调整自动 Vacuum。

24.1.1. Vacuum 操作基础 #

PostgreSQLVACUUM命令需要定期处理每个表,原因如下:

  1. 回收或重用更新或删除的行所占用的磁盘空间。
  2. 更新PostgreSQL 查询规划器使用的的数据统计信息。
  3. 更新可见性映射,这可以加快仅索引扫描的速度。
  4. 防止由于事务ID回绕多事务ID回绕而导致的旧数据丢失。

每个原因都决定了执行不同频率和范围的VACUUM操作,如下节所述。

有两种VACUUM变体:标准VACUUMVACUUM FULLVACUUM FULL可以回收更多磁盘空间,但运行速度慢得多。此外,标准形式的VACUUM可以与生产数据库操作并行运行。(诸如SELECTINSERTUPDATEDELETE之类的命令将继续正常工作,尽管在 Vacuum 操作期间,您将无法使用诸如ALTER TABLE之类的命令修改表的定义。)VACUUM FULL需要对它正在处理的表进行ACCESS EXCLUSIVE锁,因此不能与表的其他使用并行进行。因此,通常管理员应努力使用标准VACUUM并避免使用VACUUM FULL

VACUUM会产生大量I/O流量,这可能会导致其他活动会话的性能下降。可以调整一些配置参数以减少后台 Vacuum 操作对性能的影响——请参阅第 19.4.4 节

24.1.2. 恢复磁盘空间 #

PostgreSQL中,更新或删除行不会立即删除该行的旧版本。这种方法对于获得多版本并发控制(MVCC,请参阅第 13 章)的好处是必要的:在该行版本仍可能对其他事务可见时,不得删除它。但最终,过时的或已删除的行版本不再对任何事务感兴趣。然后必须回收它所占用的空间以供新行重用,以避免磁盘空间需求无限增长。这是通过运行VACUUM来完成的。

标准形式的VACUUM会删除表和索引中的死行版本,并将空间标记为可供将来重用。但是,它不会将空间返回给操作系统,除非在表末尾的一个或多个页面完全空闲并且可以轻松获得独占表锁的特殊情况下。相反,VACUUM FULL通过写入表文件的完整新版本(没有死空间)来主动压缩表。这最大限度地减少了表的大小,但可能需要很长时间。它还需要额外的磁盘空间来存放表的新的副本,直到操作完成。

常规 Vacuum 操作的通常目标是经常执行标准VACUUM,以避免需要VACUUM FULL。自动 Vacuum 守护进程尝试以这种方式工作,实际上永远不会发出VACUUM FULL。在这种方法中,其理念不是将表保持在其最小大小,而是保持磁盘空间的稳态使用:每个表占用的空间相当于其最小大小加上 Vacuum 运行之间使用的空间。尽管可以使用VACUUM FULL将表缩小到其最小大小并将磁盘空间返回给操作系统,但如果表将来会再次增长,这样做就没有多大意义。因此,对于维护更新频繁的表,适度频繁的标准VACUUM运行比不频繁的VACUUM FULL运行是一种更好的方法。

一些管理员更喜欢自己安排 Vacuum 操作的时间,例如在负载较低的时候(例如晚上)完成所有工作。根据固定时间表执行 Vacuum 操作的困难在于,如果某个表的更新活动出现意外激增,它可能会膨胀到需要VACUUM FULL才能真正回收空间的程度。使用自动 Vacuum 守护进程可以缓解此问题,因为守护进程会根据更新活动动态安排 Vacuum 操作的时间。除非您的工作负载非常可预测,否则完全禁用守护进程是不明智的。一种可能的折衷方案是设置守护进程的参数,使其仅对异常繁重的更新活动做出反应,从而防止事情失控,而计划的VACUUM则应在负载典型时完成大部分工作。

对于不使用自动 Vacuum 的用户,一种典型的方法是在低使用期间每天安排一次数据库范围的VACUUM,并根据需要补充更新频繁的表的更频繁的 Vacuum 操作。(一些更新率极高的安装每隔几分钟就会对其最繁忙的表进行一次 Vacuum 操作。)如果您在集群中有多个数据库,请不要忘记对每个数据库进行VACUUM操作;程序vacuumdb可能会有所帮助。

提示

当表包含大量死行版本(由于大量更新或删除活动导致)时,简单的VACUUM可能不令人满意。如果您有这样的表,并且需要回收它占用的多余磁盘空间,则需要使用VACUUM FULL,或者使用CLUSTERALTER TABLE的某个表重写变体。这些命令会重写表的完整新副本并为其构建新的索引。所有这些选项都需要ACCESS EXCLUSIVE锁。请注意,它们还会临时使用大约等于表大小的额外磁盘空间,因为在新的表和索引完成之前,旧的表和索引副本无法释放。

提示

如果您有一个定期删除其所有内容的表,请考虑使用TRUNCATE来执行此操作,而不是使用DELETE后跟VACUUMTRUNCATE会立即删除表的全部内容,而无需后续的VACUUMVACUUM FULL来回收现在未使用的磁盘空间。缺点是会违反严格的MVCC语义。

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

PostgreSQL 查询规划器依赖于有关表内容的统计信息才能为查询生成良好的计划。这些统计信息由ANALYZE命令收集,该命令可以单独调用,也可以作为VACUUM中的可选步骤调用。拥有合理的准确统计信息非常重要,否则,计划选择不佳可能会降低数据库性能。

如果启用了自动vacuum守护进程,它会在表内容发生足够大的变化时自动发出ANALYZE命令。但是,管理员可能更倾向于依赖手动计划的ANALYZE操作,尤其是在已知表上的更新活动不会影响“有趣”列的统计信息的情况下。守护进程严格地将ANALYZE计划作为插入或更新的行数的函数;它不知道这是否会导致有意义的统计变化。

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

与用于空间回收的vacuuming一样,频繁更新统计信息对于频繁更新的表比对于很少更新的表更有用。但是,即使对于频繁更新的表,如果数据的统计分布变化不大,也可能不需要更新统计信息。一个简单的经验法则是考虑表中列的最小值和最大值变化多少。例如,包含行更新时间的timestamp列随着添加和更新行而其最大值会不断增加;与包含网站上访问页面URL的列相比,此类列可能需要更频繁地更新统计信息。URL列可能会同样频繁地接收更改,但其值的统计分布可能变化相对缓慢。

可以在特定的表甚至只是表的特定列上运行ANALYZE,因此如果您的应用程序需要,可以灵活地比其他统计信息更频繁地更新某些统计信息。但是,在实践中,通常最好分析整个数据库,因为它是一个快速的操作。ANALYZE使用表的行的统计随机样本,而不是读取每一行。

提示

尽管对ANALYZE频率进行每列调整可能不会产生很大的效果,但您可能会发现对ANALYZE收集的统计信息的详细程度进行每列调整是值得的。在WHERE子句中大量使用且数据分布高度不规则的列可能需要比其他列更细粒度的直方图。请参阅ALTER TABLE SET STATISTICS,或使用default_statistics_target配置参数更改数据库范围的默认值。

此外,默认情况下,关于函数的选择性的信息有限。但是,如果您创建了一个使用函数调用的统计对象或表达式索引,则会收集有关该函数的有用统计信息,这可以极大地改善使用表达式索引的查询计划。

提示

自动vacuum守护进程不会为外部表发出ANALYZE命令,因为它无法确定这在多大程度上可能有用。如果您的查询需要外部表的统计信息才能进行正确的计划,则建议根据合适的计划手动管理这些表上的ANALYZE命令。

提示

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

24.1.4. 更新可见性映射 #

Vacuum为每个表维护一个可见性映射,以跟踪哪些页面仅包含已知对所有活动事务(以及所有未来事务,直到页面再次被修改)可见的元组。这有两个目的。首先,vacuum本身可以在下次运行时跳过此类页面,因为没有需要清理的内容。

其次,它允许PostgreSQL仅使用索引来回答某些查询,而无需参考基础表。由于PostgreSQL索引不包含元组可见性信息,因此正常的索引扫描会为每个匹配的索引条目获取堆元组,以检查当前事务是否应该看到它。另一方面,仅索引扫描首先检查可见性映射。如果已知页面上的所有元组都是可见的,则可以跳过堆提取。这在大型数据集上最有用,在这些数据集中,可见性映射可以防止磁盘访问。可见性映射比堆小得多,因此即使堆非常大,它也可以轻松地被缓存。

24.1.5. 防止事务ID环绕错误 #

PostgreSQLMVCC事务语义取决于能够比较事务ID(XID)数字:插入XID大于当前事务XID的行版本处于“未来”,当前事务不应该可见。但是,由于事务ID的大小有限(32位),因此长时间运行的集群(超过40亿个事务)将遭受事务ID环绕:XID计数器环绕到零,突然之间过去的事务似乎在未来——这意味着它们的输出变得不可见。简而言之,灾难性的数据丢失。(实际上数据仍然存在,但如果你无法访问它,这并不能带来安慰。)为避免这种情况,必须至少每20亿个事务对每个数据库中的每个表进行一次vacuum。

定期vacuuming解决问题的原因是VACUUM会将行标记为冻结,表示它们是由足够久远的过去提交的事务插入的,因此插入事务的影响肯定对所有当前和未来事务可见。普通XID使用模232算术进行比较。这意味着对于每个普通XID,有两个十亿个XID是“较旧的”,有两个十亿个是“较新的”;换句话说,普通XID空间是循环的,没有端点。因此,一旦使用特定的普通XID创建了行版本,对于接下来的二十亿个事务,该行版本将显示为“过去”,无论我们谈论的是哪个普通XID。如果行版本在超过二十亿个事务后仍然存在,它将突然显示在未来。为了防止这种情况,PostgreSQL保留了一个特殊的XID,FrozenTransactionId,它不遵循正常的XID比较规则,并且始终被认为比每个普通XID都旧。冻结的行版本被视为插入XID为FrozenTransactionId,因此它们将显示为对所有普通事务“过去”,而不管环绕问题如何,因此此类行版本将一直有效,直到被删除,无论持续多长时间。

注意

在9.4之前的PostgreSQL版本中,冻结是通过实际将行的插入XID替换为FrozenTransactionId来实现的,这在行的xmin系统列中可见。较新的版本只设置一个标志位,保留行的原始xmin以供可能的取证使用。但是,在从9.4之前版本升级的数据库中,仍然可能会找到xmin等于FrozenTransactionId(2)的行。

此外,系统目录可能包含xmin等于BootstrapTransactionId(1)的行,表示它们是在initdb的第一阶段插入的。与FrozenTransactionId一样,此特殊XID被视为比每个普通XID都旧。

vacuum_freeze_min_age控制XID值必须有多旧,才能冻结带有该XID的行。如果否则将被冻结的行很快会被再次修改,则增加此设置可能会避免不必要的工作,但降低此设置会增加在表必须再次进行vacuum之前可以经过的事务数量。

VACUUM使用可见性映射来确定必须扫描表的哪些页面。通常,它会跳过没有任何死行版本的页面,即使这些页面可能仍然具有具有旧XID值的行版本。因此,正常的VACUUM不会总是冻结表中的每个旧行版本。发生这种情况时,VACUUM最终需要执行积极的vacuum,它将冻结所有合格的未冻结XID和MXID值,包括来自所有可见但并非全部冻结的页面的值。在实践中,大多数表都需要定期进行积极的vacuuming。vacuum_freeze_table_age控制VACUUM何时执行此操作:如果自上次此类扫描以来经过的事务数量大于vacuum_freeze_table_age减去vacuum_freeze_min_age,则会扫描所有可见但并非全部冻结的页面。将vacuum_freeze_table_age设置为0会强制VACUUM始终使用其积极策略。

表可以不进行vacuum的最长时间是二十亿个事务减去上次积极vacuum时vacuum_freeze_min_age的值。如果超过该时间未进行vacuum,则可能导致数据丢失。为了确保不会发生这种情况,将在任何可能包含XID比配置参数autovacuum_freeze_max_age指定的值更旧的未冻结行的表上调用autovacuum。(即使禁用了autovacuum,也会发生这种情况。)

这意味着如果表没有以其他方式进行vacuum,则将大约每autovacuum_freeze_max_age减去vacuum_freeze_min_age个事务在该表上调用autovacuum。对于定期进行空间回收vacuum的表,这没什么大不了的。但是,对于静态表(包括接收插入但没有更新或删除的表),无需进行空间回收vacuum,因此尝试最大化非常大的静态表之间强制autovacuum的间隔可能很有用。显然,可以通过增加autovacuum_freeze_max_age或减少vacuum_freeze_min_age来做到这一点。

vacuum_freeze_table_age 的有效最大值为 0.95 * autovacuum_freeze_max_age;高于此值的设置将被限制到最大值。高于 autovacuum_freeze_max_age 的值没有意义,因为此时反环绕自动清理 (anti-wraparound autovacuum) 会被触发,而 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.5GB,而 pg_commit_ts 将增长到大约 20GB。如果这与您的数据库总大小相比微不足道,建议将 autovacuum_freeze_max_age 设置为其允许的最大值。否则,请根据您愿意为 pg_xactpg_commit_ts 存储分配的空间进行设置。(默认值为 2 亿个事务,对应于大约 50MB 的 pg_xact 存储空间和大约 2GB 的 pg_commit_ts 存储空间。)

减少 vacuum_freeze_min_age 的一个缺点是它可能导致 VACUUM 做无用功:如果行随后很快被修改(导致它获取新的 XID),则冻结行版本是一种浪费时间。因此,此设置应足够大,以便在行不太可能再发生更改之前才将其冻结。

为了跟踪数据库中未冻结的最旧 XID 的年龄,VACUUM 会将 XID 统计信息存储在系统表 pg_classpg_database 中。特别是,表的 pg_class 行的 relfrozenxid 列包含最近成功推进 relfrozenxidVACUUM 结束时剩余的最旧未冻结 XID(通常是最新的积极清理)。类似地,数据库的 pg_database 行的 datfrozenxid 列是该数据库中出现的未冻结 XID 的下限——它只是数据库中每个表的 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 通常仅扫描自上次清理以来已修改的页面,但只有在扫描可能包含未冻结 XID 的表的每个页面时,才能推进 relfrozenxid。当 relfrozenxid 的年龄超过 vacuum_freeze_table_age 事务时,当使用 VACUUMFREEZE 选项时,或者当所有尚未完全冻结的页面碰巧需要清理以删除死行版本时,就会发生这种情况。当 VACUUM 扫描表中每个尚未完全冻结的页面时,它应将 age(relfrozenxid) 设置为略大于所使用的 vacuum_freeze_min_age 设置的值(比自 VACUUM 开始以来启动的事务数多)。VACUUM 会将 relfrozenxid 设置为表中剩余的最旧 XID,因此最终值可能比严格要求的新得多。如果在达到 autovacuum_freeze_max_age 之前没有对表发出推进 relfrozenxidVACUUM,则很快将强制对该表进行自动清理。

如果由于某种原因,自动清理未能从表中清除旧 XID,则当数据库中最旧的 XID 距离环绕点还有 4000 万个事务时,系统将开始发出类似这样的警告消息

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

(如提示所示,手动 VACUUM 应该可以解决此问题;但请注意,VACUUM 应由超级用户执行,否则它将无法处理系统目录,这会阻止它能够推进数据库的 datfrozenxid。)如果忽略这些警告,则系统将在距离环绕点还有不到 300 万个事务时拒绝分配新的 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_activityage(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 它们。300 万个事务的安全裕度是为了让管理员执行此操作。有关使用单用户模式的详细信息,请参阅 postgres 参考页面。

24.1.5.1. 多事务和环绕 #

多事务 ID 用于支持多个事务的行锁定。由于元组头中只有有限的空间来存储锁定信息,因此当有多个事务并发锁定一行时,该信息将被编码为“多事务 ID”(简称多事务 ID)。有关哪些事务 ID 包含在任何特定多事务 ID 中的信息分别存储在 pg_multixact 子目录中,并且只有多事务 ID 出现在元组头中的 xmax 字段中。与事务 ID 一样,多事务 ID 作为 32 位计数器和相应的存储实现,所有这些都需要仔细的老化管理、存储清理和环绕处理。还有一个单独的存储区域保存每个多事务的成员列表,该列表也使用 32 位计数器,并且也必须进行管理。

每当 VACUUM 扫描表的任何部分时,它都会将遇到的任何早于 vacuum_multixact_freeze_min_age 的多事务 ID 替换为不同的值,该值可以是零值、单个事务 ID 或更新的多事务 ID。对于每个表,pg_class.relminmxid 存储表中任何元组中可能仍然出现的最旧多事务 ID。如果此值早于 vacuum_multixact_freeze_table_age,则会强制进行积极清理。如上一节所述,积极清理意味着只会跳过已知完全冻结的页面。mxid_age() 可用于 pg_class.relminmxid 以查找其年龄。

积极的 VACUUM(无论是什么原因导致的)都**保证**能够推进表的 relminmxid。最终,随着所有数据库中所有表的扫描以及其最旧的多事务值的推进,可以删除旧的多事务的磁盘存储。

作为安全措施,对于任何多事务年龄大于 autovacuum_multixact_freeze_max_age 的表,都会发生积极的清理扫描。此外,如果多事务成员占用的存储空间超过 2GB,则所有表的积极清理扫描将更频繁地发生,从具有最旧多事务年龄的表开始。即使自动清理在名义上被禁用,这两种类型的积极扫描也会发生。

与 XID 案例类似,如果自动清理未能从表中清除旧的 MXID,则当数据库中最旧的 MXID 距离环绕点还有 4000 万个事务时,系统将开始发出警告消息。并且,就像 XID 案例一样,如果忽略这些警告,则系统将在距离环绕点还有不到 300 万个时拒绝生成新的 MXID。

当MXID耗尽时,恢复正常操作的方式与XID耗尽时非常相似。请遵循上一节中的相同步骤,但以下列差异:

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

24.1.6. 自动VACUUM守护进程 #

PostgreSQL有一个可选但强烈推荐的功能,称为autovacuum(自动VACUUM),其目的是自动执行VACUUMANALYZE命令。启用后,autovacuum会检查插入、更新或删除大量元组的表。这些检查使用统计信息收集功能;因此,除非将track_counts设置为true,否则无法使用autovacuum。在默认配置中,已启用autovacuum,并且相关配置参数已适当地设置。

autovacuum守护进程实际上包含多个进程。有一个持久性守护进程,称为autovacuum启动器,它负责为所有数据库启动autovacuum工作进程。启动器会跨时间分配工作,尝试在每个数据库中每autovacuum_naptime秒启动一个工作进程。(因此,如果安装有N个数据库,则每autovacuum_naptime/N秒启动一个新的工作进程。)最多允许autovacuum_max_workers个工作进程同时运行。如果要处理的数据库超过autovacuum_max_workers个,则第一个工作进程完成后将处理下一个数据库。每个工作进程将检查其数据库中的每个表,并根据需要执行VACUUM和/或ANALYZElog_autovacuum_min_duration可以设置为监控autovacuum工作进程的活动。

如果多个大型表在短时间内都变得符合真空条件,则所有autovacuum工作进程都可能长时间占用真空这些表。这将导致其他表和数据库无法进行真空,直到有工作进程可用为止。单个数据库中工作进程的数量没有限制,但工作进程会尝试避免重复其他工作进程已完成的工作。请注意,正在运行的工作进程的数量不计入max_connectionssuperuser_reserved_connections限制。

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

vacuum threshold = vacuum base threshold + vacuum scale factor * number of tuples

其中真空基本阈值为autovacuum_vacuum_threshold,真空比例因子为autovacuum_vacuum_scale_factor,元组数量为pg_class.reltuples

如果自上次真空以来插入的元组数量超过定义的插入阈值,则也会对该表进行真空,该阈值定义为

vacuum insert threshold = vacuum base insert threshold + vacuum insert scale factor * number of tuples

其中真空插入基本阈值为autovacuum_vacuum_insert_threshold,真空插入比例因子为autovacuum_vacuum_insert_scale_factor。此类真空操作可能允许将表的部分标记为全部可见,并允许冻结元组,这可以减少后续真空操作所需的工作量。对于接收INSERT操作但没有或几乎没有UPDATE/DELETE操作的表,降低表的autovacuum_freeze_min_age可能会有益,因为这可能允许元组被早期真空操作冻结。过时元组的数量和插入元组的数量是从累积统计信息系统中获取的;它是一个最终一致的计数,由每个UPDATEDELETEINSERT操作更新。如果表的relfrozenxid值超过vacuum_freeze_table_age事务,则会执行积极的真空操作以冻结旧元组并推进relfrozenxid;否则,只会扫描自上次真空以来已修改的页面。

对于分析,使用类似的条件:阈值定义为

analyze threshold = analyze base threshold + analyze scale factor * number of tuples

与自上次ANALYZE以来插入、更新或删除的元组总数进行比较。

分区表不直接存储元组,因此不会由autovacuum处理。(Autovacuum确实像处理其他表一样处理表分区。)不幸的是,这意味着autovacuum不会在分区表上运行ANALYZE,这会导致引用分区表统计信息的查询产生次优计划。您可以通过在首次填充分区表时手动在这些表上运行ANALYZE,以及在分区数据分布发生重大变化时再次运行ANALYZE来解决此问题。

autovacuum无法访问临时表。因此,应通过会话SQL命令执行适当的vacuum和analyze操作。

默认阈值和比例因子取自postgresql.conf,但可以按表分别覆盖它们(以及许多其他autovacuum控制参数);有关更多信息,请参阅存储参数。如果已通过表的存储参数更改了某个设置,则在处理该表时将使用该值;否则将使用全局设置。有关全局设置的更多详细信息,请参阅第 19.10 节

当多个工作进程运行时,autovacuum成本延迟参数(请参阅第 19.4.4 节)会在所有正在运行的工作进程之间平衡,以便无论实际运行多少个工作进程,对系统的总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完成。

提交更正

如果您在文档中看到任何不正确的内容,与您对特定功能的体验不符,或者需要进一步说明,请使用此表单报告文档问题。