第 24 章 日常数据库维护任务
目录
和任何数据库软件一样,PostgreSQL 为了获得最佳性能,需要定期执行某些任务。这里讨论的任务是必需的,但它们本质上是重复性的,因此可以很容易地用标准工具实现自动化,例如 cron 脚本或 Windows 的 Task Scheduler。建立合适的脚本并检查其是否成功执行,是数据库管理员的职责。
一个显而易见的维护任务,是按固定计划创建数据的备份副本。没有最近的备份,在灾难(磁盘故障、火灾、误删关键表等)发生后就没有恢复的可能。PostgreSQL 提供的备份和恢复机制在第 25 章中有详细讨论。
另一大类维护任务是定期对数据库进行“清理”。这一活动在第 24.1 节中讨论。与之密切相关的是更新查询规划器将会使用的统计信息,这在第 24.1.3 节中讨论。
另一项可能需要定期关注的任务是日志文件管理。这在第 24.3 节中讨论。
check_postgres 可用于监控数据库健康状况并报告异常情况。check_postgres 能与 Nagios 和 MRTG 集成,但也可以单独运行。
和某些其他数据库管理系统相比,PostgreSQL 的维护工作量较小。不过,适当地关注这些任务,将大大有助于确保你愉快而高效地使用该系统。
24.1. 日常清理 #
PostgreSQL 数据库需要一种称为清理的周期性维护。对于许多安装,让第 24.1.6 节所述的自动清理守护进程执行清理就足够了。为了在你的场景中获得最佳效果,你可能需要调整其中描述的自动清理参数。有些数据库管理员希望用手工管理的
VACUUM 命令来补充甚至取代该守护进程的工作,这类命令通常由
cron 或 Task Scheduler
脚本按计划执行。要正确设置手工管理的清理,理解下面几小节讨论的问题至关重要。依赖自动清理的管理员也不妨略读这一材料,以帮助理解和调整自动清理。
24.1.1. 清理基础 #
PostgreSQL 的 VACUUM 命令必须定期处理每个表,原因如下:
- 回收或再利用被更新或删除的行所占用的磁盘空间。
- 更新 PostgreSQL 查询规划器使用的数据统计信息。
- 更新可见性映射,以加快仅索引扫描。
- 防止由于事务 ID 回卷或多事务 ID 回卷而丢失非常旧的数据。
针对上述不同目的,需要以不同的频率和范围执行 VACUUM 操作,下面几节会详细说明。
VACUUM 有两种变体:标准的 VACUUM 和
VACUUM FULL。VACUUM FULL 可以回收更多磁盘空间,但执行速度要慢得多。此外,标准形式的 VACUUM 可以与生产数据库操作并行运行。(SELECT、INSERT、UPDATE 以及
DELETE 等命令会继续正常工作,不过在表被清理时,你将不能使用
ALTER TABLE 等命令修改该表的定义。)VACUUM FULL 需要对正在处理的表持有
ACCESS EXCLUSIVE 锁,因此不能与对该表的其他使用并行进行。一般来说,管理员应尽量使用标准 VACUUM 并避免
VACUUM FULL。
VACUUM 会产生大量 I/O 流量,这可能导致其他活动会话性能变差。可以调整一些配置参数来降低后台清理对性能的影响,参见第 19.4.4 节。
24.1.2. 回收磁盘空间 #
在 PostgreSQL 中,对某一行执行
UPDATE 或 DELETE 时,不会立即移除该行的旧版本。这种方法对于获得多版本并发控制(MVCC,见第 13 章)的好处是必需的:当行版本仍可能对其他事务可见时,就不能删除它。但最终,过时或已删除的行版本将不再是任何事务关心的对象。必须回收它占用的空间,以供新行重用,从而避免磁盘空间需求无限增长。这是通过运行 VACUUM
完成的。
标准形式的 VACUUM 会移除表和索引中的死行版本,并将空间标记为可供将来重用。不过,它不会把空间归还给操作系统,除非出现一种特殊情况:表尾的一个或多个页面完全空闲,并且能够轻松获得一个排他表锁。相比之下,VACUUM FULL 会主动压实表,它会写出一个不含死空间的全新表文件版本。这会将表的大小降到最小,但可能耗时很长。在操作完成之前,它还需要额外的磁盘空间来存放表的新副本。
例行清理的通常目标,是足够频繁地执行标准 VACUUM,从而避免需要 VACUUM FULL。自动清理守护进程就是按照这种方式工作的,事实上它永远不会发出 VACUUM FULL。这种方法的思路不是让表始终保持在最小尺寸,而是让磁盘空间的使用维持在稳态:每个表占用的空间相当于其最小尺寸,再加上两次清理之间又被用掉的空间。虽然 VACUUM FULL 可以把表重新缩小到最小尺寸,并把磁盘空间归还给操作系统,但如果该表之后还会再次增长,这样做意义并不大。因此,对于维护被频繁更新的表,与其不常执行 VACUUM FULL,不如适度频繁地执行标准 VACUUM。
有些管理员喜欢自己安排清理,例如在夜间负载较低时完成全部工作。按固定时间表执行清理的难点在于,如果某个表的更新活动出现意外高峰,它可能膨胀到确实需要 VACUUM FULL
才能回收空间的程度。使用自动清理守护进程可以缓解这个问题,因为守护进程会根据更新活动动态安排清理。除非工作负载极其可预测,否则完全禁用守护进程是不明智的。一种可能的折中办法是设置守护进程参数,使其只对异常繁重的更新活动作出反应,从而防止情况失控,而在负载正常时,则期望定期调度的
VACUUM 完成大部分工作。
对于不使用自动清理的人,一个典型的做法是在低使用时段每天安排一次面向整个数据库的
VACUUM,并在必要时更频繁地清理那些更新特别频繁的表。(某些更新率极高的安装,甚至会每隔几分钟就清理一次最繁忙的表。)如果你在一个集簇中有多个数据库,不要忘记对每一个数据库都执行
VACUUM;程序 vacuumdb 可能会有帮助。
提示
当一个表由于大规模更新或删除活动而包含大量死行版本时,普通的 VACUUM
可能并不能令人满意。如果你有这样一个表,并且需要回收它所占用的多余磁盘空间,就需要使用 VACUUM FULL,或者改用 CLUSTER,又或者使用 ALTER TABLE 的某一种重写表变体。这些命令会重写整个表的新副本,并为其构建新的索引。所有这些选项都需要 ACCESS EXCLUSIVE 锁。还要注意,它们会临时额外占用大约等于该表大小的磁盘空间,因为在新表和新索引完成之前,旧的表和索引副本都不能被释放。
提示
如果你有一个表,其全部内容会被定期删除,考虑使用 TRUNCATE,而不是先 DELETE 再 VACUUM。TRUNCATE 会立即移除表的全部内容,而不需要后续再执行
VACUUM 或 VACUUM 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 计数器回到零,于是原本属于过去的事务会突然看起来像在未来,这意味着它们的结果会变得不可见。简而言之,就是灾难性的数据丢失。(实际上数据仍然在那里,但如果你无法访问,这也没什么安慰。)为了避免这种情况,必须至少每 20 亿个事务对每个数据库中的每个表执行一次清理。
周期性清理之所以能解决这个问题,是因为 VACUUM 会将行标记为冻结,表示这些行由一个很久以前就已提交的事务插入,因此该插入事务的效果肯定对所有当前和未来事务都可见。普通 XID 使用模
232 算术进行比较。这意味着,对任意普通 XID,都有 20 亿个 XID 比它“更老”,另有 20 亿个比它“更新”;换句话说,普通 XID 空间是一个没有端点的环。因此,一旦某个行版本带着某个普通 XID 创建出来,那么无论当前谈论的是哪个普通 XID,在接下来的 20 亿个事务中,这个行版本都会看起来处于“过去”。如果该行版本在超过 20 亿个事务之后仍然存在,它就会突然看起来处于未来。为防止这种情况,PostgreSQL 保留了一个特殊 XID,FrozenTransactionId,它不遵循普通 XID 的比较规则,并且始终被视为比每一个普通 XID 都更老。被冻结的行版本会被当作其插入 XID 是
FrozenTransactionId,因此无论是否发生回卷问题,它们对于所有普通事务都会显得处于“过去”,从而这类行版本在被删除之前一直有效,不管要持续多久。
注意
在 9.4 之前的 PostgreSQL 版本中,冻结是通过实际把一行的插入 XID
替换为 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_freeze_table_age 控制 VACUUM 何时执行这种操作:如果距离上一次此类扫描经过的事务数,大于 vacuum_freeze_table_age 减去 vacuum_freeze_min_age,就会扫描全部可见但未全部冻结的页面。将 vacuum_freeze_table_age 设为 0,会强制 VACUUM 对所有扫描都采用这种更激进的策略。
一个表可以不清理的最长时间,是 20 亿个事务减去上次激进清理时的 vacuum_freeze_min_age 值。超过这个期限仍不清理,就可能丢失数据。为确保不会发生这种情况,对于可能包含未冻结行、且其 XID 年龄大于配置参数 autovacuum_freeze_max_age 所指定年龄的任何表,都会调用自动清理。(即使禁用了自动清理,也会如此。)
这意味着,如果一个表本来不会因为其他原因被清理,大约每经过
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_min_age 个事务,就会在该表上触发一次自动清理。对于那些为了回收空间而经常清理的表,这一点并不重要。然而,对于静态表(包括只接收插入、但没有更新或删除的表),并不需要为了回收空间而清理,因此设法尽可能拉大这些非常大的静态表上强制自动清理之间的间隔,可能会很有用。显然,这可以通过增大 autovacuum_freeze_max_age 或减小
vacuum_freeze_min_age 来做到。
减小 vacuum_freeze_min_age 的一个缺点是,它可能导致
VACUUM 做无用功:如果某个行版本随后不久就被修改(因而获得新的 XID),冻结它就是浪费时间。因此,该设置应足够大,使得行在不太可能再次发生变化之前不会被冻结。
为了跟踪数据库中最老未冻结 XID 的年龄,VACUUM 会在系统表
pg_class 和 pg_database
中存储 XID 统计信息。具体来说,某个表在 pg_class 中对应行的
relfrozenxid 列,记录了上一次对该表执行的激进
VACUUM 所使用的冻结截止 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 到当前事务 XID 之间的事务数。
VACUUM 通常只扫描自上次清理以来被修改过的页面,但只有当表中每一个可能包含未冻结 XID 的页面都被扫描时,relfrozenxid 才会被推进。当
relfrozenxid 的年龄超过 vacuum_freeze_table_age 个事务、使用了 VACUUM 的
FREEZE 选项,或者所有尚未全部冻结的页面碰巧都需要清理以移除死行版本时,就会发生这种情况。当 VACUUM 扫描了表中每个尚未全部冻结的页面时,它应把 age(relfrozenxid) 设为略高于所用
vacuum_freeze_min_age 设置的值(更高的部分等于自 VACUUM 开始以来已启动的事务数)。如果直到达到
autovacuum_freeze_max_age 之前,该表都没有执行一次能推进
relfrozenxid 的 VACUUM,那么很快就会被强制执行一次自动清理。
如果由于某种原因,自动清理未能清除表中的旧 XID,当数据库中最老的 XID 距离回卷点只剩 1100 万个事务时,系统就会开始发出类似以下的警告:
WARNING: database "mydb" must be vacuumed within 10985967 transactions HINT: To avoid a database shutdown, execute a database-wide VACUUM in that database.
(按照提示,手动执行 VACUUM 应能解决问题;但请注意,VACUUM 必须由超级用户执行,否则无法处理系统目录,也就无法推进数据库的 datfrozenxid。)如果忽略这些警告,一旦距离回卷只剩不到 100 万个事务,系统就会关闭并拒绝启动任何新事务:
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 万个事务的安全余量,是为了让管理员可以手动执行必需的 VACUUM 命令,在不丢失数据的情况下恢复。不过,系统一旦进入安全关闭模式,就不会再执行命令,因此唯一的办法是停止服务器,再以单用户模式启动服务器,执行 VACUUM。单用户模式下不会强制进入关闭模式。有关使用单用户模式的详细信息,参见 postgres 参考页面。
24.1.5.1. 多事务与回卷 #
多事务(multitransaction,MultiXact)ID 用于支持多个事务对行加锁。由于元组头中用于存储锁信息的空间有限,当有多个事务同时锁定一行时,这些信息会被编码为一个“多事务 ID”,简称 multixact ID。某个特定多事务 ID 包含哪些事务 ID 的信息,会单独存放在 pg_multixact 子目录中,而元组头的
xmax 字段中只出现多事务 ID。和事务 ID 一样,多事务 ID 也是以 32 位计数器及其对应存储实现的,因此同样需要仔细处理老化管理、存储清理和回卷问题。还有一个单独的存储区域保存每个多事务的成员列表,它也使用 32 位计数器,因此同样必须被管理。表 9.74 中介绍的系统函数 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 扫描都能推进该表的值。最终,随着所有数据库中的所有表都被扫描,并推进其最老的多事务值,较老多事务的磁盘存储就可以被移除。
作为一项安全措施,对于多事务年龄大于 autovacuum_multixact_freeze_max_age 的任何表,都会执行激进清理扫描。如果已使用的成员存储空间超过可寻址存储空间的 50%,还会从多事务年龄最大的表开始,逐步对所有表执行激进清理扫描。即使名义上禁用了自动清理,这两类激进扫描也都会发生。
24.1.6. 自动清理守护进程 #
PostgreSQL 具有一个可选但强烈推荐的特性,称为自动清理,其目的是自动执行
VACUUM 和 ANALYZE 命令。启用后,自动清理会检查那些已经积累了大量插入、更新或删除元组的表。这些检查依赖于统计收集功能;因此,除非 track_counts 被设置为 true,否则无法使用自动清理。在默认配置下,自动清理是启用的,并且相关配置参数也设置得比较合适。
所谓“自动清理守护进程”实际上由多个进程组成。其中有一个持久运行的守护进程,称为自动清理启动器,负责为所有数据库启动自动清理工作进程。启动器会把工作分散在时间上,力图在每个数据库中每隔 autovacuum_naptime 秒启动一个工作进程。(因此,如果安装中有 N 个数据库,就会每隔
autovacuum_naptime/N 秒启动一个新的工作进程。)同时最多允许运行 autovacuum_max_workers 个工作进程。如果需要处理的数据库数量多于 autovacuum_max_workers,那么一旦第一个工作进程结束,就会处理下一个数据库。每个工作进程会检查其数据库中的每个表,并根据需要执行 VACUUM 和/或 ANALYZE。可以设置 log_autovacuum_min_duration 来监控自动清理工作进程的活动。
如果多个大型表在很短时间内都变得需要清理,那么所有自动清理工作进程都可能会长期忙于清理这些表。这会导致其他表和数据库在某个工作进程空闲下来之前都得不到清理。单个数据库中的工作进程数量没有上限,但工作进程会尽量避免重复其他工作进程已经完成的工作。注意,正在运行的工作进程数量不计入 max_connections 或 superuser_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 可能有益,因为这可能让元组在更早的清理中被冻结。失效元组数和插入元组数来自统计收集器;这些计数只是近似值,由每次 UPDATE,DELETE 和 INSERT 操作更新。(之所以只是近似值,是因为高负载下可能丢失部分信息。)如果表的 relfrozenxid 值的年龄超过 vacuum_freeze_table_age 个事务,就会执行激进清理,冻结旧元组并推进 relfrozenxid;否则,只扫描自上次清理以来被修改过的页面。
对于分析操作,也使用了一个类似的条件:其阈值定义如下:
analyze threshold = analyze base threshold + analyze scale factor * number of tuples
该阈值会与自上次 ANALYZE 以来插入、更新或删除的元组总数进行比较。
分区表不直接存储元组,因此不会被自动清理处理。(自动清理会像处理其他表一样处理表分区。)不幸的是,这意味着自动清理不会对分区表执行 ANALYZE,而这可能导致引用分区表统计信息的查询生成次优计划。你可以在分区表首次填充时手工运行
ANALYZE 来绕过这个问题,并在其各分区中的数据分布发生显著变化时再次运行。
自动清理无法访问临时表。因此,应通过会话 SQL 命令执行适当的清理和分析操作。
默认阈值和尺度因子取自 postgresql.conf,但也可以按表覆盖它们(以及许多其他自动清理控制参数);详情见 Storage Parameters。如果某个设置已经通过表的存储参数修改,那么处理该表时就使用那个值;否则使用全局设置。关于全局设置的更多细节,见第 19.10 节。
当多个工作进程同时运行时,自动清理的代价延迟参数(见第 19.4.4 节)会在所有运行中的工作进程之间“平衡”,这样无论实际运行了多少个工作进程,系统承受的总 I/O 影响都相同。不过,任何正在处理那些为每表存储参数
autovacuum_vacuum_cost_delay 或
autovacuum_vacuum_cost_limit 显式设置过值的表的工作进程,都不会被纳入这个均衡算法。
自动清理工作进程通常不会阻塞其他命令。如果某个进程试图获取与自动清理持有的
SHARE UPDATE EXCLUSIVE 锁冲突的锁,那么该加锁操作会中断自动清理。关于冲突锁模式,见表 13.2。但是,如果自动清理正在运行,其目的是防止事务 ID 回卷(也就是说,在 pg_stat_activity
视图中,自动清理查询名以 (to prevent wraparound) 结尾),则自动清理不会被自动中断。
警告
定期运行需要获取与 SHARE UPDATE EXCLUSIVE 锁冲突的锁的命令(例如 ANALYZE),实际上可能会使自动清理永远无法完成。