24.1. 常规吸尘

PostgreSQL 数据库需要定期维护,称为* vacuuming 。对于许多安装,让中描述的 autovacuum 守护程序*执行吸尘就足够了。您可能需要调整此处描述的自动抽真空参数,以获得适合您情况的最佳结果。一些数据库 Management 员会希望用手动 Management 的VACUUM命令来补充或替换守护程序的活动,这些命令通常是根据 cron 或 Task Scheduler 脚本的计划执行的。为了正确设置手动 Management 的清理,必须理解接下来几个小节中讨论的问题。依靠自动抽真空的 Management 员可能仍希望略读此材料,以帮助他们理解和调整自动抽真空。

24 .1.1. 吸尘基础

PostgreSQL 的VACUUM命令必须定期处理每个表,原因如下:

  • 恢复或重用更新或删除的行占用的磁盘空间。

  • 更新 PostgreSQL 查询计划程序使用的数据统计信息。

  • 要更新可见度图,可加快index-only scans

  • 为了防止由于“事务 ID 环绕”或“多重 ID 环绕”而丢失非常旧的数据。

这些原因中的每一个都要求执行频率和范围变化的VACUUM操作,如以下小节所述。

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

VACUUM创建了大量的 I/O 流量,这可能会导致其他活动会话的性能下降。有一些配置参数可以调整,以减少后台清理对性能的影响-参见Section 19.4.4

24 .1.2. 恢复磁盘空间

在 PostgreSQL 中,某行的UPDATEDELETE不会立即删除该行的旧版本。这种方法对于获得多版本并发控制(MVCC,请参阅Chapter 13)的好处是必不可少的:必须删除行版本,而其他 Transaction 仍可能看到该行版本。但是最终,过时或已删除的行版本不再对任何事务有意义。然后必须占用它占用的空间,以供新行重用,以避免磁盘空间需求无限制地增长。这是通过运行VACUUM完成的。

VACUUM的标准形式删除表和索引中的死行版本,并标记可用于将来重用的空间。但是,它不会将空间返回给 os,除非在特殊情况下,表末尾的一个或多个页面变得完全空闲,并且可以轻松获得独占表锁。相反,VACUUM FULL通过编写没有死角的表文件的全新版本来主动压缩表。这样可以最大程度地减少表的大小,但是会花费很长时间。在操作完成之前,表的新副本还需要额外的磁盘空间。

常规吸尘的通常目标是经常执行标准VACUUM,从而避免需要VACUUM FULL。 autovacuum 守护程序尝试以这种方式工作,并且实际上永远不会发出VACUUM FULL。在这种方法中,其想法不是将表保持在最小大小,而是保持磁盘空间的稳态使用:每个表占用的空间等于其最小大小,而且在两次清理之间占用了很多空间。尽管VACUUM FULL可用于将表缩小到最小大小并将磁盘空间返回给 os,但是如果表将来会再次增长,则没有什么意义。因此,对于维护大量更新的表,中度频繁的标准VACUUM运行是比不频繁的VACUUM FULL运行更好的方法。

一些 Management 员更喜欢安排自己的吸尘工作,例如在负载低的晚上进行所有工作。根据固定的时间表进行清理的困难在于,如果表的更新活动意外增加,则可能会肿到VACUUM FULL实际上是回收空间所必需的程度。使用 autovacuum 守护程序可以缓解此问题,因为该守护程序会动态安排清理以响应更新活动。除非您的工作负荷非常可预测,否则完全禁用该守护程序是不明智的。一种可能的折衷办法是设置守护程序的参数,以使其仅对异常繁重的更新活动做出反应,从而避免麻烦,而通常情况下,计划的VACUUM则可以完成大部分工作。

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

Tip

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

Tip

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

24 .1.3. 更新计划者统计信息

PostgreSQL 查询计划程序依靠有关表内容的统计信息来生成良好的查询计划。这些统计信息是通过ANALYZE命令收集的,该命令可以单独调用,也可以在VACUUM中作为可选步骤调用。拥有合理准确的统计信息很重要,否则计划选择不当可能会降低数据库性能。

如果启用了 autovacuum 守护程序,则只要表的内容已发生足够的更改,它将自动发出ANALYZE命令。但是,Management 员可能更喜欢依靠手动调度的ANALYZE操作,尤其是在已知表上的更新活动不会影响“有趣”列的统计信息的情况下。守护程序严格根据插入或更新的行数来调度ANALYZE;它不知道是否会导致有意义的统计变化。

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

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

Tip

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

同样,默认情况下,关于功能选择性的信息有限。但是,如果创建使用函数调用的表达式索引,则将收集有关该函数的有用统计信息,这可以大大改善使用表达式索引的查询计划。

Tip

autovacuum 守护程序不会为外部表发出ANALYZE命令,因为它无法确定可能有用的频率。如果您的查询需要统计外表的统计信息以进行适当计划,则最好以适当的时间表在这些表上运行手动 Management 的ANALYZE命令。

24 .1.4. 更新可见性 Map

Vacuum 为每个表维护一个visibility map,以跟踪哪些页面仅包含已知对所有活动事务(以及所有将来的事务可见,直到再次修改该页面)可见的 Tuples。这有两个目的。首先,真空本身可以在下一次运行时跳过此类页面,因为没有什么需要清理的。

其次,它允许 PostgreSQL 仅使用索引来回答某些查询,而无需引用基础表。由于 PostgreSQL 索引不包含 Tuples 可见性信息,因此常规索引扫描会为每个匹配的索引条目获取堆 Tuples,以检查当前事务是否应看到它。另一方面,index-only scan首先检查可见性图。如果知道页面上的所有 Tuples 都是可见的,则可以跳过堆提取。这对于可视性 Map 可能阻止磁盘访问的大型数据集最为有用。可见性图远小于堆,因此即使堆很大,也可以轻松对其进行缓存。

24 .1.5. 防止事务 ID 绕回失败

PostgreSQL 的MVCC事务语义取决于能否比较事务 ID(XID)编号:插入 XID 大于当前事务 XID 的行版本是“将来的”,并且当前事务不可见。但是由于事务 ID 的大小有限(32 位),所以长时间运行(超过 40 亿个事务)的集群将遭受“事务 ID 环绕” *:XID 计数器会归零,并且所有突然的 Transaction 过去似乎是将来-这意味着它们的输出将变得不可见。简而言之,灾难性的数据丢失。 (实际上,数据仍然存在,但是如果您无法获得它,那真是令人感到 console.)为避免这种情况,有必要对每个数据库中的每个表进行至少 20 亿次事务清理。

定期清理可以解决此问题的原因是VACUUM将行标记为* frozen *,这表明它们是由过去已提交足够远的事务插入的,因此该插入事务的影响对于所有当前对象和所有对象都是可见的。FutureTransaction。普通 XID 使用 232 模运算进行比较。这意味着对于每个普通的 XID,有 20 亿个“较旧”的 XID 和 20 亿个“较新”的 XID;换句话说,正常的 XID 空间是循环的,没有端点。因此,一旦创建具有特定普通 XID 的行版本,无论我们在谈论哪个普通 XID,该行版本对于接下来的 20 亿个事务而言似乎都是“过去的”。如果行版本在经过 20 亿笔 Transaction 后仍然存在,那么它将突然出现在将来。为了避免这种情况,PostgreSQL 保留了一个特殊的 XID FrozenTransactionId,它不遵循常规 XID 比较规则,并且始终被认为比每个常规 XID 都旧。冻结的行版本被视为插入的 XID 为FrozenTransactionId,因此它们对于所有常规事务而言似乎都是“过去的”,而与环绕问题无关,因此,无论删除了多长时间,此类行版本在删除之前都有效。是。

Note

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

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

vacuum_freeze_min_age控制在冻结带有该 XID 的行之前,XID 值必须多大。如果很快将再次修改原本将冻结的行,则增加此设置可以避免不必要的工作,但是减小此设置会增加必须再次清理表之前可以经过的事务数。

VACUUM使用visibility map来确定必须扫描表的哪些页面。通常,即使那些页面可能仍然具有带有旧 XID 值的行版本,它也会跳过没有任何死行版本的页面。因此,普通VACUUM不会总是冻结表中的每个旧行版本。 VACUUM会定期执行“积极的清除”,仅跳过那些既不包含死行也不包含任何未冻结的 XID 或 MXID 值的页面。 vacuum_freeze_table_age控制VACUUM何时执行此操作:如果自上次此类扫描以来已通过的事务数大于vacuum_freeze_table_age减去vacuum_freeze_min_age,则扫描所有可见页面而不是所有冻结页面。将vacuum_freeze_table_age设置为 0 会强制VACUUM对所有扫描使用这种更具侵略性的策略。

一张桌子可以抽空的最大时间是 20 亿笔 Transaction 减去上次主动真空时的vacuum_freeze_min_age值。如果将其抽空的时间更长,则可能导致数据丢失。为确保不会发生这种情况,将对可能包含未冻结行且其 XID 早于配置参数autovacuum_freeze_max_age指定的年龄的任何表上调用 autovacuum。 (即使禁用了自动真空,也会发生这种情况.)

这意味着,如果不对表进行清理,则大约每autovacuum_freeze_max_age减去vacuum_freeze_min_age个事务将在其上调用一次 autovacuum。对于定期清理以回收空间的桌子,这并不重要。但是,对于静态表(包括接收插入但不进行更新或删除的表),不需要进行空间回收的清理,因此尝试在非常大的静态表上最大化强制自动清理之间的间隔可能很有用。显然,可以通过增大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设置为其最大允许值 20 亿,则可以预期pg_xact增长到大约半 GB,而pg_commit_ts增长到大约 20GB。如果这与您的数据库总大小相比微不足道,则建议将autovacuum_freeze_max_age设置为其最大允许值。否则,请根据您愿意为pg_xactpg_commit_ts存储的内容进行设置。 (默认情况下为 2 亿笔 Transaction,转换为大约 50MB 的pg_xact存储空间和大约 2GB 的pg_commit_ts存储空间.)

减少vacuum_freeze_min_age的一个缺点是,它可能导致VACUUM做无用的工作:如果行版本此后不久被修改(导致其获取新的 XID),则冻结行版本是浪费时间。因此,该设置应足够大,以使行不会冻结,直到它们不可能再更改为止。

要跟踪数据库中最老的未冻结 XID 的年龄,VACUUM将 XID 统计信息存储在系统表pg_classpg_database中。特别是,表pg_class行的relfrozenxid列包含该表的最后一个主动VACUUM使用的冻结截止 XID。 XID 早于该截止 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 到当前 Transaction 的 XID 的 Transaction 数量。

VACUUM通常仅扫描自上次清理以来已修改的页面,但是relfrozenxid仅在扫描表中可能包含未冻结的 XID 的每个页面时才前进。当relfrozenxid的使用期限超过vacuum_freeze_table_age事务的使用期限,使用VACUUMFREEZE选项或所有尚未完全冻结的页面需要清理以删除死行版本时,就会发生这种情况。 VACUUM扫描表中尚未完全冻结的每个页面时,应将age(relfrozenxid)设置为仅比所使用的vacuum_freeze_min_age设置稍高的值(更多的是自VACUUM启动以来启动的事务数)。如果直到达到autovacuum_freeze_max_age为止,都没有在表上发出relfrozenxid -advancing VACUUM,将很快为该表强制执行自动清理。

如果由于某种原因 autovacuum 无法从表中清除旧的 XID,则当数据库中最旧的 XID 从环绕点到达一千万笔事务时,系统将开始发出如下警告消息:

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

(如提示所示,手册VACUUM应该可以解决此问题;但是请注意VACUUM必须由超级用户执行,否则它将无法处理系统目录,因此无法推进数据库的datfrozenxid.)如果出现这些警告,被忽略时,一旦剩余 Transaction 少于一百万次,系统将关闭并拒绝启动任何新 Transaction,直到折回:

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.

拥有 100 万笔 Transaction 的安全裕度,可让 Management 员通过手动执行所需的VACUUM命令来恢复而不会丢失数据。但是,由于一旦进入安全关闭模式,系统将不会执行命令,因此执行此操作的唯一方法是停止服务器并以单用户模式启动服务器以执行VACUUM。在单用户模式下不强制执行关机模式。有关使用单用户模式的详细信息,请参见postgres参考页。

24 .1.5.1. Multixacts 和环绕

  • Multixact ID *用于支持多个事务的行锁定。由于 Tuples 头中仅存在有限的空间来存储锁定信息,因此,只要有多个事务同时锁定一行,该信息就会被编码为“多个事务 ID”或简称为“多重事务 ID”。有关在任何特定的多重处理 ID 中包含哪些事务 ID 的信息分别存储在pg_multixact子目录中,并且只有多重处理 ID 出现在 Tuples 标题的xmax字段中。与事务 ID 一样,多重 ID 被实现为 32 位计数器和相应的存储,所有这些都需要仔细的老化 Management,存储清理和环绕处理。有一个单独的存储区,用于保存每个 multixact 中的成员列表,该存储区还使用 32 位计数器,并且还必须对其进行 Management。

VACUUM每次扫描表的任何部分时,都会用一个不同的值替换它遇到的早于vacuum_multixact_freeze_min_age的任何多重 ID,这些值可以是零值,单个事务 ID 或较新的多重 ID。对于每个表,pg_classrelminmxid存储仍然出现在该表的任何 Tuples 中的最旧的多重 ID。如果此值早于vacuum_multixact_freeze_table_age,则将强制使用主动真空。如前一部分所述,主动清除意味着仅跳过那些已知为全冻结的页面。 mxid_age()可用于pg_classrelminmxid找到它的年龄。

积极的VACUUM扫描,无论是什么原因引起的扫描,都可以提高该表的值。最终,由于扫描了所有数据库中的所有表并提高了它们的最旧多重值,因此可以删除磁盘上旧版本的多重存储。

作为一种安全设备,任何多表年龄大于autovacuum_multixact_freeze_max_age的表都将进行主动真空扫描。如果使用的成员存储空间量超过了可寻址存储空间的 50%,则从具有最老的 multixact-age 的表开始,所有表还将逐步进行积极的真空扫描。即使自动真空被禁用,这两种主动扫描都将发生。

24 .1.6. 自动真空守护程序

PostgreSQL 有一个可选的但强烈推荐的功能,称为* autovacuum *,其目的是自动执行VACUUMANALYZE命令。启用后,自动清理将检查具有大量插入,更新或删除的 Tuples 的表。这些检查使用统计信息收集工具;因此,除非将track_counts设置为true,否则不能使用自动真空。在默认配置中,启用了自动清理并适当设置了相关配置参数。

“ autovacuum 守护程序”实际上包含多个进程。有一个持续的守护进程,称为* autovacuum 启动器*,它负责为所有数据库启动* autovacuum worker 进程。启动器将跨时间分配工作,尝试每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以来已废弃的 Tuples 数超过“真空阈值”,则该表将被清除。真空阈值定义为:

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

其中真空基准阈值为autovacuum_vacuum_threshold,真空比例因子为autovacuum_vacuum_scale_factor,Tuples 数为pg_classreltuples。过时的 Tuples 数是从统计信息收集器中获得的;它是每个UPDATEDELETE操作更新的半精确计数。 (这只是半准确的,因为某些信息可能会在重负载下丢失.)如果表的relfrozenxid值大于vacuum_freeze_table_age个事务旧的值,则会执行主动清除以冻结旧的 Tuples 并提前relfrozenxid;否则,仅扫描自上次吸尘以来已修改的页面。

为了进行分析,使用了类似的条件:阈值定义为:

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

将与上一个ANALYZE以来插入,更新或删除的 Tuples 总数进行比较。

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

默认阈值和比例因子取自postgresql.conf,但是可以按表覆盖它们(和许多其他自动真空控制参数);有关更多信息,请参见Storage Parameters。如果通过表的存储参数更改了设置,则在处理该表时将使用该值;否则,将使用该值。否则,将使用全局设置。有关全局设置的更多详细信息,请参见Section 19.10

当多个 Worker 正在运行时,自动真空成本延迟参数(请参见Section 19.4.4)在所有正在运行的 Worker 之间“平衡”,因此,无论实际运行的 Worker 数量如何,对系统的总 I/O 影响都是相同的。但是,在平衡算法中不考虑处理已设置每表autovacuum_vacuum_cost_delayautovacuum_vacuum_cost_limit存储参数的任何工作表。

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

Warning

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