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

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 环绕MultiXact ID 环绕导致的老旧数据丢失。

每个原因都要求执行不同频率和范围的VACUUM操作,正如以下小节中所述。

有两种变体形式的VACUUM:标准VACUUMVACUUM FULLVACUUM FULL能回收更多磁盘空间,但运行更慢。此外,VACUUM标准形式可以与生产数据库操作并行运行。(诸如SELECTINSERTUPDATE以及DELETE这样的命令会正常持续使用,但当对某个表进行清理时,您将无法使用类似ALTER TABLE这样的命令修改其定义。) VACUUM FULL需要一个ACCESS EXCLUSIVE锁对其正在操作的表,因此不能与表的其他使用并行执行。所以,管理员在通常情况下应该致力于使用标准VACUUM,并避免VACUUM FULL

VACUUM 会产生大量的 I/O 流量,这可能导致其他活动会话的性能不良。有可供调节的配置参数以减少后台 Vacuum 的性能影响——参见 第 19.4.4 节

24.1.2. 回收磁盘空间 #

PostgreSQL 中,行的 UPDATEDELETE 不会立即删除行的旧版本。这种方法对于获得多版本并发控制 (MVCC) 的益处是必要的,参见 第 13 章:当行版本对其他事务仍然可能可见时,不可删除它。但最终,过时的或已删除的行版本将不再被任何事务所关心。随后其所占据的空间必须被回收以供新行重用,从而避免磁盘空间需求不受限制地增长。运行 VACUUM 可以做到这一点。

VACUUM 的标准形式删除表和索引中的死行版本并将空间标记为可供日后重用。然而,它不会将该空间归还到操作系统,除非在一个特殊情况下,即表尾部的某个或某些页面完全变空并且可以轻松地获得一个排它的表锁。相比之下,VACUUM FULL 通过编写一个新的、没有无用空间的表文件版本来主动压缩表。这最大程度地减小了表的尺寸,但可能花费很长时间。在操作完成之前,它还需要用于表新副本的额外磁盘空间。

例行真空吸尘的通常目标是对标准VACUUM执行足够频繁的操作,从而无须使用VACUUM FULL。autovacuum守护程序尝试以此方式工作,事实上,绝不会发出VACUUM FULL。在此方法中,其目的并非使表保持在最小尺寸,而是维持磁盘空间使用量的稳定状态:每个表占据相当于其最小尺寸大小的空间,加上在 Vacuum 运行期间消耗的任何大小的空间。尽管VACUUM FULL可用于将表缩小至其最小尺寸并将磁盘空间返还给操作系统,但如果该表将来再次增大,则这样做毫无意义。因此,对于维护频繁更新的表来说,适度频繁的标准VACUUM运行是一种比不频繁的VACUUM FULL运行更好的方法。

一些管理员更喜欢自行安排真空吸尘,例如在负载低时在夜间完成所有工作。按照固定时间安排进行真空吸尘的困难在于,如果某一表格的更新活动意外激增,那么它可能会膨胀到这样的程度,以至于确实需要VACUUM FULL来回收空间。由于守护程序会根据更新活动动态安排 Vacuum 的时间,因此使用 autovacuum 守护程序可以缓解此问题。除非您的工作负载高度可预测,否则不建议完全禁用该守护程序。一种可能的折衷办法是设置守护程序的参数,以便它只对异常繁重的更新活动作出响应,从而防止事情失控,同时预计在负载正常时安排的VACUUM完成大部分工作。

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

提示

当表包含大量已删除行版本(由于大量更新或删除活动所致)时,一般的 VACUUM 可能不令人满意。如果你有这样的表并且需要回收它所占用的过量磁盘空间,你将需要使用 VACUUM FULL,或备选方案 CLUSTERALTER 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. 更新可见性映射 #

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

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

24.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 来实现的,而该 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 操作的时间为两百亿个事务,减去上次强制 vacuum 时 vacuum_freeze_min_age 值。如果未执行 vacuum 操作的时间超过此时间,则可能导致数据丢失。为了确保这种情况不会发生,将自动 vacuum 应用于可能包含未冻结行的任何表,且该行的 XID 早于配置参数 autovacuum_freeze_max_age 中指定的时间。(即使已禁用自动真空,也会发生这种情况。)

这意味着,如果其他情况下未对表执行 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 的值是没有意义的,因为反循环自动清理将会在此处触发,并且对于 0.95 的倍数在发生此情况之前会留出一些空间让手动 VACUUM 能够运行。根据经验法则,vacuum_freeze_table_age 应设置为略低于 autovacuum_freeze_max_age 的值,留下足够的空隙来运行定期的 VACUUM 或由正常删除和更新活动触发的自动清理。如果设置得太近,即使最近才对表进行了清理来回收空间,也可能导致反循环自动清理,而较低的值会导致更频繁的激进清理。

增加 autovacuum_freeze_max_age(以及 vacuum_freeze_table_age)的唯一缺点是数据库集群的 pg_xactpg_commit_ts 子目录将会占用更多空间,因为它必须将所有事务的提交状态和(如果 track_commit_timestamp 已启用)时间戳存储至 autovacuum_freeze_max_age 水平面。提交状态使用每笔事务两个位,因此,如果 autovacuum_freeze_max_age 被设置为其允许的最大值二十亿,可以预期 pg_xact 增大到大约五百兆字节,pg_commit_ts 增大到大约 20GB。如果与你的总数据库大小相比,这是微不足道的,则建议将 autovacuum_freeze_max_age 设置为其允许的最大值。否则,根据你愿意为 pg_xactpg_commit_ts 存储分配多少来设置。(默认值两亿笔事务,相当于大约 50MB 的 pg_xact 存储和大约 2GB 的 pg_commit_ts 存储。)

减小 vacuum_freeze_min_age 的一个缺点是它可能导致 VACUUM 执行无用的工作:冻结行版本是浪费时间的操作,如果行在不久之后被修改(使其获取新的 XID),冻结行版本便是浪费时间的操作。因此,该设置应该足够大,以便在不太可能再发生更改之前不会冻结行。

为了追踪某个数据库中最旧的未冻结 XID 的期限,VACUUM 将 XID 统计数据存储在系统表 pg_classpg_database 中。具体来说,某个表的 pg_class 行的 relfrozenxid 列包含在最近一次成功推进 relfrozenxidVACUUM 结束时,剩余最旧的未冻结 XID(通常是最近一次强力 VACUUM)。类似地,某个数据库的 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 通常只扫描上次清理以来已经修改的页面,但 relfrozenxid 只有在扫描该表的所有可能包含未冻结 XID 的页面时才能推进。这在以下情况下会发生:当 relfrozenxidvacuum_freeze_table_age 事务老时,当使用 VACUUMFREEZE 选项时,或者当所有未完全冻结的页面恰好需要清理以移除已删除的行版本时。当 VACUUM 扫描表中所有未完全冻结的页面时,它应当将 age(relfrozenxid) 设置为仅比已使用的 vacuum_freeze_min_age 设置稍大一点的值(比自 VACUUM 开始以来已开始的事务数更多)。 VACUUM 会将 relfrozenxid 设置为表中剩余的最旧的 XID,因此最终值可能比严格要求的要新得多。如果在 relfrozenxid 推进 VACUUM 发出之后,直到 autovacuum_freeze_max_age 达到之前,对该表没有发出任何 VACUUM,不久后就会强制为该表进行自动清理。

如果自动清理程序由于某种原因无法从表中清除旧 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.

(一个手动 VACUUM 应该可以解决问题,正如提示所建议的;但请注意 VACUUM 应该由超级用户执行,否则它将无法处理系统目录,这会阻止它推进数据库的 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 命令仍然可以正常运行。请注意,与早期版本中有时推荐的不同,为了恢复正常操作,没有必要或期望停止后级管理员或进入单用户模式。相反,请按照以下步骤操作

  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。三百万事务的安全余量可以让管理员执行此操作。有关使用单用户模式的详细信息,请参阅 postgres 参考页。

24.1.5.1. 多事务和回绕 #

多事务 ID 用于支持多个事务的 row locking。由于元组头中存储锁信息的可用空间有限,因此每当有多个事务同时锁定期时,该信息将被编码为 多事务 ID,或简称多事务 ID。有关特定多事务 ID 中包含哪些事务 ID 的信息将单独存储在 pg_multixact 子目录中,并且只有多事务 ID 才会显示在元组头中的 xmax 字段中。与事务 ID 一样,多事务 ID 也被实现为一个 32 位计数器和相应的存储,所有这些都需要仔细的老化管理、存储清理和回绕处理。有一个单独的存储区域存放每个多事务中成员的列表,该列表也使用 32 位计数器,并且也必须进行管理。

每当 VACUUM 扫描一张表的一部分时,它将把遇到的任何 multixact ID 替换为小于 vacuum_multixact_freeze_min_age 的其他值,可以是零值、单个事务 ID 或更新的 multixact ID。对于每个表,pg_class.relminmxid 会存储仍然出现在该表的任何元组中的最旧的可能 multixact ID。如果此值小于 vacuum_multixact_freeze_table_age,会强制执行激进的 VACUUM。如前一节中讨论的那样,激进的 VACUUM 意味着只跳过已知完全冻结的那些页面。可对 pg_class.relminmxid 使用 mxid_age() 来查找其年龄。

激进的 VACUUM,无论由什么原因导致,都能 保证推进表的 relminmxid。最终,随着所有数据库中的所有表被扫描且其最旧的 multixact 值被推进,可以移除针对旧 multixact 的磁盘存储。

作为一项安全机制,对于 multixact-age 大于 autovacuum_multixact_freeze_max_age 的任何表都会执行激进的 VACUUM 扫描。另外,如果 multixacts 成员占用的存储超过 2GB,激进的 VACUUM 扫描将更频繁地发生在所有表上,从 multixact-age 最旧的表开始。即使 autovacuum 在名义上已禁用,这两种激进扫描都会发生。

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

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

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

24.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来解决此问题,即在首次填充它们时运行,以及在各分区中的数据分布发生重大变化时再次运行。

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

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

在运行多个工作进程时,自动真空消耗延迟参数(请参阅第 19.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)冲突的锁的命令会有效阻止自动真空完成。