Redrock Postgres 搜索 英文
版本: 9.3 / 9.4 / 9.5 / 9.6 / 10 / 11 / 12 / 13 / 14 / 15 / 16

25.1. 例行真空 #

25.1.1. 真空基础
25.1.2. 恢复磁盘空间
25.1.3. 更新计划器统计信息
25.1.4. 更新可见性映射
25.1.5. 防止事务 ID 环绕故障
25.1.6. 自动真空守护进程

PostgreSQL 数据库需要定期维护,称为真空。对于许多安装,让自动真空守护进程执行真空就足够了,如第 25.1.6 节中所述。您可能需要调整此处描述的自动真空参数,以获得最佳结果。一些数据库管理员希望通过手动管理的VACUUM命令来补充或替换守护进程的活动,这些命令通常由cron任务计划程序脚本根据计划执行。要正确设置手动管理的真空,了解下一小节中讨论的问题至关重要。依赖自动真空的管理员可能仍然希望浏览此材料,以帮助他们理解和调整自动真空。

25.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 流量,这会导致其他活动会话的性能不佳。可以通过调整配置参数来减少后台 vacuum 的性能影响 — 请参阅 第 20.4.4 节

25.1.2. 回收磁盘空间 #

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

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

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

部分管理员更喜欢自己安排真空清理,例如在负载较低时在夜间完成所有工作。根据固定时间表进行真空清理的难点在于,如果某个表在更新活动中意外激增,则它可能会膨胀到 VACUUM FULL 确实有必要回收空间的程度。使用自动真空清理守护进程可以缓解此问题,因为守护进程会根据更新活动动态安排真空清理。除非您的工作负载极易预测,否则不建议完全禁用守护进程。一种可能的折衷办法是设置守护进程的参数,以便它仅对异常繁重的更新活动做出反应,从而防止事情失控,而计划的 VACUUM 预计在负载典型时完成大部分工作。

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

提示

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

提示

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

25.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 以保持统计信息是最新的。

25.1.4. 更新可见性映射 #

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

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

25.1.5. 防止事务 ID 环绕故障 #

PostgreSQLMVCC 事务语义依赖于比较事务 ID (XID) 编号:插入 XID 大于当前事务 XID 的行版本是 在将来,并且当前事务不应该看到它。但是由于事务 ID 的大小有限(32 位),因此长时间运行(超过 40 亿个事务)的集群将遭受事务 ID 环绕:XID 计数器环绕到零,并且突然间过去的事务似乎在将来——这意味着它们的输出变得不可见。简而言之,灾难性的数据丢失。(实际上数据仍然存在,但如果你无法获取它,那也没什么安慰。)为了避免这种情况,有必要在每两个十亿个事务中至少对每个数据库中的每个表进行一次真空。

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

注意

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

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

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

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

表可以不进行 VACUUM 的最长时间为 20 亿个事务减去上次激进 VACUUM 时的vacuum_freeze_min_age值。如果超过此时间不进行 VACUUM,则可能导致数据丢失。为了确保不会发生这种情况,将对任何可能包含 XID 早于配置参数autovacuum_freeze_max_age指定的年龄的未冻结行的表调用自动 VACUUM。(即使禁用了自动 VACUUM,也会发生这种情况。)

这意味着,如果一张表没有以其他方式进行 VACUUM,则大约每 autovacuum_freeze_max_age 减去 vacuum_freeze_min_age 个事务就会在表上调用自动 VACUUM。对于定期进行 VACUUM 以回收空间的表而言,这一点无关紧要。但是,对于静态表(包括接收插入但没有更新或删除的表),无需进行 VACUUM 以回收空间,因此在非常大的静态表上最大化强制自动 VACUUM 之间的间隔非常有用。显然,可以通过增加 autovacuum_freeze_max_age 或减少 vacuum_freeze_min_age 来实现这一点。

vacuum_freeze_table_age 的有效最大值为 0.95 * autovacuum_freeze_max_age;高于该值的设置将被限制为最大值。高于 autovacuum_freeze_max_age 的值没有意义,因为反环绕自动 VACUUM 会在该点触发,而 0.95 乘数留出了一些余地,以便在发生这种情况之前运行手动 VACUUM。根据经验法则,vacuum_freeze_table_age 应设置为略低于 autovacuum_freeze_max_age 的值,留出足够的间隙,以便在该窗口中运行定期计划的 VACUUM 或由正常删除和更新活动触发的自动 VACUUM。将其设置得太接近可能会导致反环绕自动 VACUUM,即使该表最近已进行 VACUUM 以回收空间,而较低的值会导致更频繁的激进 VACUUM。

增加 autovacuum_freeze_max_age(以及 vacuum_freeze_table_age)的唯一缺点是数据库集群的 pg_xactpg_commit_ts 子目录将占用更多空间,因为它必须存储提交状态以及所有事务(如果启用了 track_commit_timestamp)的时间戳,一直到 autovacuum_freeze_max_age 水平。提交状态对每个事务使用两位,因此如果 autovacuum_freeze_max_age 设置为允许的最大值 20 亿,则 pg_xact 预计将增长到大约 500MB,而 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(通常是最近一次激进的 VACUUM)结束时最旧的剩余未冻结 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 通常只扫描自上次 vacuum 以来已修改的页面,但 relfrozenxid 只能在扫描可能包含未冻结 XID 的表的所有页面时才能推进。当 relfrozenxidvacuum_freeze_table_age 事务旧超过 vacuum_freeze_table_age 时,当 VACUUMFREEZE 选项被使用时,或当所有尚未完全冻结的页面恰好需要 vacuum 来移除死行版本时,就会发生这种情况。当 VACUUM 扫描表中尚未完全冻结的每个页面时,它应将 age(relfrozenxid) 设置为略大于所使用的 vacuum_freeze_min_age 设置的值(比自 VACUUM 启动以来启动的事务数多)。VACUUM 将把 relfrozenxid 设置为表中保留的最旧 XID,因此最终值可能比严格要求的要新得多。如果在达到 autovacuum_freeze_max_age 之前未对表发出 relfrozenxid 推进 VACUUM,则将很快强制对表进行自动 vacuum。

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

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

(如提示所建议,手动 VACUUM 应能解决问题;但请注意,VACUUM 应由超级用户执行,否则它将无法处理系统目录,这会阻止它推进数据库的 datfrozenxid。)如果忽略这些警告,则在环绕之前只剩下不到三百万个事务时,系统将拒绝分配新 XID

ERROR:  database is not accepting commands to avoid wraparound data loss in database "mydb"
HINT:  Stop the postmaster and vacuum that database in single-user mode.

在这种情况下,任何已在进行中的事务都可以继续,但只能启动只读事务。修改数据库记录或截断关系的操作将失败。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. 一旦恢复正常操作,请确保在目标数据库中正确配置自动清理,以避免将来出现问题。

注意

在早期版本中,有时需要停止后端并以单用户模式 VACUUM 数据库。在典型场景中,这不再是必需的,并且应尽可能避免,因为它涉及关闭系统。它也更危险,因为它禁用了旨在防止数据丢失的事务 ID 环绕保护措施。在此场景中使用单用户模式的唯一原因是,如果您希望 TRUNCATEDROP 不需要的表以避免需要 VACUUM 它们。三百万事务安全裕度存在是为了让管理员执行此操作。有关使用单用户模式的详细信息,请参见 postgres 参考页。

25.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,则强制执行激进的 VACUUM。如前一节所述,激进的 VACUUM 意味着只跳过已知已全部冻结的页面。可以在 pg_class.relminmxid 上使用 mxid_age() 来查找其年龄。

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

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

与 XID 情况类似,如果自动 VACUUM 无法清除表中的旧 MXID,则当数据库最旧的 MXID 达到环绕点四十万个事务时,系统将开始发出警告消息。而且,就像 XID 情况一样,如果忽略这些警告,则当距离环绕点只剩下不到三百万个 MXID 时,系统将拒绝生成新的 MXID。

当 MXID 耗尽时,可以恢复正常操作,方式与 XID 耗尽时非常相似。按照前一节中的相同步骤进行操作,但有以下区别

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

25.1.6. 自动清理守护进程 #

PostgreSQL 有一项可选但强烈推荐的功能,称为 自动清理,其目的是自动执行 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 以来过期的元组数超过了 清理阈值,则会清理该表。清理阈值定义为

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 以来插入、更新或删除的元组总数进行比较。

分区表不直接存储元组,因此不会被自动清理处理。(自动清理确实会像处理其他表一样处理表分区。)遗憾的是,这意味着自动清理不会对分区表运行 ANALYZE,这会导致引用分区表统计信息的查询的计划不理想。您可以通过在分区表首次填充时手动运行 ANALYZE,以及每当其分区中的数据分布发生显着变化时再次运行 ANALYZE 来解决此问题。

自动清理无法访问临时表。因此,应通过会话 SQL 命令执行适当的清理和分析操作。

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

当多个工作进程正在运行时,自动清理成本延迟参数(请参阅 第 20.4.4 节)在所有正在运行的工作进程中“平衡”,因此无论实际运行的工作进程数量如何,对系统产生的总 I/O 影响都是相同的。但是,在平衡算法中不考虑处理表且其按表 autovacuum_vacuum_cost_delayautovacuum_vacuum_cost_limit 存储参数已设置的工作进程。

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

警告

定期运行获取与 SHARE UPDATE EXCLUSIVE 锁冲突的锁的命令(例如,ANALYZE)可以有效地阻止自动清理完成。