23.1. Routine Vacuuming日常清理

的PostgreSQL数据库需要定期维护为vacuuming。 对于许多安装,由autovacuum daemon执行清空操作足够了,其描述在Section 23.1.5。 要获得你想要的最佳结果,你可能需要调整所述的 autovacuuming 参数。某些数据库管理员将想要使用VACUUM命令, 补充或替换管理守护进程的活动,其将由cronTask Scheduler 脚本。要设置手动管理正确地清除,有必要了解在下面几个小节讨论的问题。那些依赖autovacuuming的管理员可能仍然希望略读这些资料, 帮助他们了解和调整autovacuuming。

23.1.1. 清理基础

PostgreSQLVACUUM 命令定期来处理每个表有以下几个原因:

  1. 通过更新或删除行恢复或重复使用所占用的磁盘空间。

  2. 通过PostgreSQL查询规划器更新数据统计分析。

  3. transaction ID wraparound可以防止由于非常旧的数据丢失

这些原因都决定了要执行不同的频率和范围的VACUUM操作,在下面的小节解释。

有两种VACUUM:标准的VACUUMVACUUM FULLVACUUM FULL可以回收更多的磁盘空间,但运行速度要慢得多。 VACUUM标准方式可以与产生的数据库操作并行运行。(像SELECTINSERTUPDATEDELETE命令可以工作正常, 但是当正在清理该表时,你不能使用像ALTER TABLE命令修改表定义。) VACUUM FULL需要在表上的一个排斥锁才能工作,并因此不能与其它使用该表的并行。 因此一般情况下管理员应尽量用标准的 VACUUM,并期避免使用VACUUM FULL

VACUUM 创建大量的I/O交互,这可能会导致其它活动会话性能表现不好。 这里有个配置参数,可以调整,以减少清理性能的影响, 请参阅 Section 18.4.3

23.1.2. 恢复磁盘空间

在正常的 PostgreSQL 操作里,对一行的 UPDATEDELETE 并未立即删除旧版本的数据行。这个方法 对于获取多版本并发控制的好处是必要的(MVCC, 详情参阅章Chapter 13):如果一个行的版本仍有可能被其它事务看到, 那么你就不能删除它。但到了最后,不会有任何事务对过期的或者已经删除的 行感兴趣。而它占据的空间必须为那些新行的使用而回收,以避免对磁盘空间 的无休止需求。这件事是通过运行 VACUUM 实现的。

标准形式的VACUUM删除死行版本在表和索引标记以便将来重用的可用空间。 然而,它不会返回操作系统的空间,除非在特殊情况下,一个或多个表页成为完全自由,而且可以很容易获得一个独占表锁。 与此相反,VACUUM FULL积极压缩表,通过写完成没有死空间的表文件的新版本。这可以最大限度地减少表的大小,但可能需要很长一段时间。 这还需要额外的磁盘空间表的新副本,直到操作完成。 这可以最大限度地减少表的大小,但可能需要很长一段时间。这还需要额外的磁盘空间表的新副本,直到操作完成。

做标准VACUUM通常能够满足用日常清理的目标避免需要做VACUUM FULL。 autovacuum守护进程试图以这种方式工作,其实从不做 VACUUM FULL。 在这种方法中,这个想法不是保持表为最小尺寸,但要保持稳定状态的磁盘空间使用情况: 每张表占用的空间相当于其最小尺寸,加,获取到清理之间使用的多少空间。 尽管VACUUM FULL可用于收缩表为其最小尺寸,并返回磁盘空间给操作系统。 有一点在此不多如果表只会在将来再次增长。因此,适度频次运行标准VACUUM 要较好于 不频发的运行VACUUM FULL保持大量更新表。

有些管理员喜欢调度的清理本身,例如,晚上当负荷低时,做所有的工作。 按照一个固定的时间做清理表的困难是,如果一个表有更新活动的意外大增。 可能会庞大到确实需要VACUUM FULL回收空间。使用autovacuum守护进程,缓解这一问题, 因为守护进程的时间表清理动态响应更新的活动。完全禁用守护进程是不明智的,除非你有一个非常可预见的工作量。 一个可能的折衷办法就是设置守护进程的参数,以便它将只对异常繁重更新活动作出反应,因此可以从失控保持这些事情, 当典型的负载时,调度VACUUM还期望做批量的工作。

对于那些不使用autovacuum,一个典型的方法是在低使用时一天做一次的调度数据库范围的VACUUM, 补充下更新频繁的表有必要更频繁的清理。 (有些安装更新率非常高,清理的最繁忙的表往往每隔几分钟一次。) 如果你有个多数据库的集群,别忘了每一个的VACUUMvacuumdb这个程序有帮助的。

Tip: 当一个表中包含大量死行版本,作为一个大规模的更新或删除活动的结果,平常的VACUUM未必令人满意。 如果你有这样的表,并且你需要回收过多占用的磁盘空间,你将需要使用VACUUM FULL,或者选择CLUSTER或者 ALTER TABLE表重写的变体之一。这些命令重写整个表的新副本,并建立新的索引。 所有这些选项需要独占锁。 请注意他们也暂时使用额外的磁盘空间大约等于大小的表中,因为不能发布的表和索引的旧副本,直到新的完成。

Tip: 如果你有一个表,它的内容经常被完全删除, 那么可以考虑用 TRUNCATE 而不是后面跟着 VACUUMDELETETRUNCATE 立即删除整个表的内容,而不要求 随后的 VACUUMVACUUM FULL 来恢复现在未使用的磁盘空间。

23.1.3. 更新规划器统计

PostgreSQL 的查询规划器依赖一些有关 表内容的统计信息用以为查询生成好的规划。这些统计是通过 ANALYZE命令获得的,你可以直接调用这条命令, 也可以把它当做 VACUUM 里的一个可选步骤来调用。 拥有合理准确的统计是非常重要的,否则,选择了恶劣的规划很可能 降低数据库的性能。

当一张表的内容足够改变时,如果启用了 autovacuum 守护进程,将发出ANALYZE命令。 不过,管理员可能更愿意依靠手工设定的ANALYZE选项,尤其,如果已知表的更新活动当没有生效在"感兴趣" 的表字段统计。 这个守护进程调度是严格的作为一个插入或更新的行数的功能;它没有了解是否会导致有意义的统计变化。

和为了回收空间做清理一样,经常更新统计信息也是对更新频繁的表 更有用。不过,即使是更新非常频繁的表,如果它的数据的统计分布 并不经常改变,那么也不需要更新统计信息。一条简单的拇指定律就 是想想表中字段的最大跟最小值改变的幅度。比如,一个包含行更新 时间的 timestamp 字段将随着行的追加和更新稳定 增长最大值;这样的字段可能需要比那些包含访问网站的 URL 的字段 更频繁一些更新统计信息。那些 URL 字段可能改变得一样频繁, 但是其数值的统计分布的改变相对要缓慢得多。

我们可以在特定的表,甚至是表中特定的字段上运行ANALYZE ,所以如果你的应用有需求的话,可以对某些信息更新得比其它信息更 频繁。不过,在实际中,这种做法的有用性是值得怀疑的。因为 ANALYZE 使用了统计学上的随机采样的方法进行行采样, 而不是把每一行都读取进来,所以即使在大表上也是一项相当快的操作。

Tip: 尽管用 ANALYZE 针对每个字段进行挖掘的方式可能不是很实用, 但你可能还是会发现值得针对每个字段对 ANALYZE 收集的统计 信息的详细级别进行调整。那些经常在 WHERE 子句里使用的字段 如果有非常不规则的数据分布,那么就可能需要比其它字段更细致的数据图表。 参阅ALTER TABLE SET STATISTICS 或更改数据库的使用默认在 default_statistics_target的配置参数 。

此外,缺省情况下 选择这些函数是有限制信息的。 但是,如果你创建表达式的索引,它使用一个函数调用,可以极大地提高使用表达式索引的查询计划收集有关函数的有用的统计数据。

23.1.4. 避免事务ID重叠造成的问题

PostgreSQL 的 MVCC 事务语意依赖于比较 事务 ID(XID)的数值:一条带有大于当前事务 XID 的插入 XID 的行版本是"在将来" ,并且不应为当前事务可见。 但是因为事务 ID 的大小有限(在我们写这些的时候是 32 位), 如果集群一次运行的时间很长(大于 40 亿次事务),那么它就要受到 transaction ID wraparound的折磨:XID 计数器回到零位, 然后突然间所有以前的事务就变成看上去是在将来的,这意味着它们的 输出将变得可见。简而言之,可怕的数据丢失。实际上数据仍然在那里, 但是如果你无法获取数据,这么说也只是自我安慰罢了。若要避免此问题, 有必要以清理的每个数据库的每张表每20亿的事务至少一次。

周期性的运行 VACUUM 可以解决这个问题的原因在于 PostgreSQL 可以辨别特殊的 XID (FrozenXID)。这个 XID 总是被认为比任何普通的 XID 旧。 普通的 XID 使用 模-231 算法进行比较。这就意味着对于每个普通的 XID , 总是有二十亿个 XID 是"older"以及二十亿个 XID "newer";表达这个意思 的另外一个方法是普通的 XID 空间是没有终点的环。因此, 一旦某行带着特定的普通 XID 创建出来,那么该行将在以后的 二十亿次事务中表现得是"过去时",而不管我们说的是哪个普通 XID 。如果该行在超过二十亿次事务之后仍然存在,那么它就 会突然变成在将来的行。为了避免数据丢失,老的行必须在 到达二十亿次事务标记之前的某个时候赋予 FrozenXID。 一旦它被赋予了这个特殊的 XID ,那么它们在所有普通事务 面前表现为"过去时",而不管事务 ID 是否重叠,因此这样的 行不管保存多长时间,直到删除之前都会完好。这个 XID 的重新赋值是 VACUUM 控制的。

vacuum_freeze_min_age controls how old an XID value has to be before it's replaced with FrozenXID. 较大的值,此设置的保留事务性信息更长,而较小的值增加可以经过改值前必须再次清理表事务的数目。

通常VACUUM跳过没有任何死行版本的页,但是那些页可能仍有旧XID值的行版本。 为了确保所有的旧XID值由FrozenXID替换,需要整表扫描。当VACUUM做这些时,vacuum_freeze_table_age控制: vacuum_freeze_table_age controls when VACUUM does that:如果表尚未完全扫描 vacuum_freeze_table_age减去 vacuum_freeze_min_age事务,则强制全表扫描。 设置它为0,强制VACUUM总是扫描所有页,有效的忽略可见映射。

表在清理之前允许执行的最大事务次数是 20 亿减去上次清理时的 vacuum_freeze_min_age 值。如果超过这个限制就很 可能造成数据丢失。为了保证数据安全,必须在任何可能包含旧于 autovacuum_freeze_max_age 指定的 XID 的表上调用autovacuum 。(甚至在autovacuum 被禁用的情况下也可以调用。)

这就意味着,每 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将没有任何意义,因为无论如何此时 将触发反重叠的autovacuum,在这发生前0.95乘数留下了一些闲余空间要运行VACUUM来处理。 作为一个经验法则,vacuum_freeze_table_age应该设置为低于autovacuum_freeze_max_age的某个值, 留下足够的差距因此定期调度VACUUM或者在该窗口中运行正常删除和更新活动的引发autovacuum。 设置太近,可能会导致反重叠autovacuums,即使表最近清理过回收空间,而较低的值导致更多频繁的全表扫描。

增加 autovacuum_freeze_max_age(以及一起的 vacuum_freeze_table_age )的唯一不利之处在于数据库 集群的pg_clog 子目录将会占用更多空间,因为它必须 为所有 autovacuum_freeze_max_age 之后的事务存储 提交状态。每个事务提交状态使用 2 字节,因此如果 autovacuum_freeze_max_age 的值略小于 20 亿, pg_clog 将会增加到大约 500M 。如果这个尺寸比起 你的数据库来只是小菜一碟,我们推荐你将 autovacuum_freeze_max_age 设为允许的最大值。否则,如何设置将取决于你愿意给 pg_clog 多大的空间。默认值是 2 亿,大约需要 50MB 的 pg_clog 存储空间。

减小 vacuum_freeze_min_age 的不利之处是可能导致 VACUUM 做无用功:如果行在不久之后就被修改,那么将 XID 修改 为 FrozenXID 就是在浪费时间,因为它很快就将获得一个新 的 XID 。因此这个设置应当足够大以使得行不被过早的冻结。 减小 vacuum_freeze_min_age 的另一个不利之处是事务插入或修改行的 准确细节将会很快丢失。这个信息有时迟早会派上用场,特别是数据库本 扩之后分析究竟发生了什么错误的时候。因为这两个原因,在完全静态的 表上减小这个值是不明智的。

为了跟踪数据库中最老的 XID 寿命,VACUUM 在系统表 pg_classpg_database 里存储了 事务 ID 统计。尤其是一个数据库的 pg_class 行中 的 relfrozenxid 字段包含了最后一个 VACUUM 命令使用的冻结终止 XID 。系统保证在该数据库中所有比这个终止 XID 老的普通 XID 都被 FrozenXID 代替。同样, 一个数据库的 pg_database 行中的datfrozenxid 字段是普通 XID 的下界,它只是数据库中每个表 relfrozenxid 的最小值。检查这个信息的一个便利方法是执行下面的查询

SELECT relname, age(relfrozenxid) FROM pg_class WHERE relkind = 'r';
SELECT datname, age(datfrozenxid) FROM pg_database;

age 字段用于测量从中止 XID 到当前事务 XID 的数目。

VACUUM 通常只扫描自上次清理修改的页,但是整表扫描时,relfrozenxid 只能提前。当 relfrozenxidvacuum_freeze_table_age事务旧, 使用VACUUMFREEZE选项,或者所有页发生需要清理或删除死行版本时做整表扫描。 当VACUUM扫描整表时,在结束age(relfrozenxid),其应该设置的一个比 vacuum_freeze_min_age 多一点之后使用。 (自VACUUM启动后,更多数量的事务启动)。如果该表上没有整表扫描的VACUUM直到达到autovacuum_freeze_max_age, 将会很快在该表上强制autovacuum处理。

如果从表中清理旧 XID 失败,那么当数据库的旧 XID 到达 1000 万个事务以后, 系统将发出类似下面这样的警告信息:

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。) 如果忽略了上面的警告信息,那么系统将在距离重叠小于 100 万次的 时候关闭,并且拒绝执行任何新的事务:

ERROR:  database is not accepting commands to avoid wraparound data loss in database "mydb"
HINT:  Stop the postmaster and use a standalone backend to VACUUM in "mydb".

这个 100 万的事务安全边界留下来用于让管理员在不丢失数据的情况下 进行恢复,方法是手工执行所需要的 VACUUM 命令。不过, 因为一旦进入了安全关闭模式,系统就不能再执行命令,做这件事情的 唯一的方法是停止主服务器,使用一个单独运行的后端来执行 VACUUM 。 关闭模式不会强制于独立运行的后端。参阅 postgres 手册获取有关使用独立运行后端的细节。

23.1.5. autovacuum 守护进程

PostgreSQL系统带有一个额外的可选服务进程, 叫做 autovacuum 守护进程, 它的目的是自动执行 VACUUMANALYZE 命令。 在打开这个选项之后,autovacuum 守护进程将周期性运行并且检查那些 有大量插入、更新、删除行操作的表。这些检查使用行级别的统计收集设施; 因此,除非把 track_counts设置为 true ,否则无法使用 autovacuum 守护。 在缺省配置,autovacuuming启用适当的设置和相关配置参数。

" autovacuum 守护进程"实际上包含多个进程。有一种持久性的守护进程,名叫autovacuum launcher, 其负责启动所有数据库的autovacuum worker进程。这个启动程序将跨越时间分配的工作, 尝试每隔autovacuum_naptime秒为每个数据库启动一个工作者。 (因此,如果安装的有N个数据库,则每隔autovacuum_naptime/N秒启动一个新工作者。) 允许最多autovacuum_max_workers工作者进程同时运行。 如果有超过 autovacuum_max_workers要处理的数据库, 在接下来的数据库将尽快处理的第一个工作者完成。在每个工作者进程将检查数据库中的每张表,并执行VACUUM ,需要时 ANALYZE

如果几个较大的表都有资格在短时间内清理,所有 autovacuum工作者可能成为一段长时间都忙于清除这些表。 这会导致不清理其它表和数据库直到一个工作者变得可用。在单个数据库中多少工作者可能是没有限制的,但工作者还是尽量避免重复做已完成的其它工作者的工作。 请注意运行的工作者数目不计关于max_connectionssuperuser_reserved_connections 的限制。

将总是清理那些 relfrozenxid 大于 autovacuum_freeze_max_age 的表。(这也适用于那些表的冻结最大寿命已通过修改存储参数,见下文) 否则如果上次 VACUUM 之后的过期行的数量超过了 "vacuum threshold",那么就清理该表。清理阈值定义为:

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

这里的清理基本阈值是 autovacuum_vacuum_threshold , 清理缩放系数是 autovacuum_vacuum_scale_factor , 行数是 pg_class.reltuples。 过期行的数量是从统计收集器里面获取的, 这是一个半精确的计数,由每次 UPDATEDELETE 操作更新。 半精确的原因是在重负载时有些信息可能会丢失。 如果表的relfrozenxid 值比vacuum_freeze_table_age的事务更旧, 则要冻结行和relfrozenxid做整表扫描, 否则只扫描最后一个清理后修改过的页。

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

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

它会和上次 ANALYZE 插入、更新、删除的总行数进行比较。

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

缺省的阈值和伸缩系数是从 postgresql.conf 里面 取得的,不过,可以针对每个表独立设置, 参阅节存储参数获取更多信息。 如果 pg_autovacuum 里面存在针对特定表的行,那么就使用该特定的设置; 否则使用全局设置。参阅节Section 18.9 获取有关全局设置的更多细节。

除了基本阈值和缩放系数之外,在 pg_autovacuum 里还有 6 个参数 可以为每个表进行设置。首先, autovacuum_enabled 可以设置为 false 让 autovacuum 守护进程 完全忽略某个表。这种情况下,autovacuum 只有在为了避免事务 ID 重叠必须清理整个数据库的时候才会动那个表。接下来两个参数, 清理开销延迟autovacuum_vacuum_cost_delay 和清理开销限制autovacuum_vacuum_cost_limit, ,用于针对特定的表为vacuum_freeze_min_agevacuum_freeze_table_age 特性设置数值。

当多个工作者正在运行,成本的限制是"平衡"之间的所有正在运行的工作者,使系统总的影响是相同的, 不管实际运行的工作者数目。