↑↓ 选择 ↵ 打开 ⌫ 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2
历史版本PostgreSQL 8.1 已于 2010 年 11 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本。

第 22 章 日常数据库维护任务

要让一个PostgreSQL服务器平稳运行,需要定期执行 一些例行维护工作。这里讨论的任务本质上是重复性的,可以很容易地用标准 Unix 工具(例如 cron 脚本)实现自动化。但建立 合适的脚本并检查其是否成功执行,是数据库管理员的职责。

一个显而易见的维护任务,是按固定计划创建数据的备份副本。没有最近的备份, 在灾难(磁盘故障、火灾、误删关键表等)发生后就没有恢复的可能。 PostgreSQL提供的备份和恢复机制在 第 23 章中有详细讨论。

另一大类维护任务是定期对数据库进行“清理”。这一活动在 第 22.1 节中讨论。

另一样可能需要定期关注的东西是日志文件管理。这在 第 22.3 节中讨论。

和某些其他数据库管理系统相比,PostgreSQL的维护工作量较小。 不过,适当地关注这些任务,将大大有助于确保你愉快而高效地使用该系统。

22.1. 日常清理 #

PostgreSQL的VACUUM命令必须定期运行,原因有以下几个:

  1. 回收被更新或删除的行所占用的磁盘空间。
  2. 更新PostgreSQL查询规划器使用的数据统计信息。
  3. 防止因事务 ID 回卷而丢失非常旧的数据。

出于上述各原因而执行的VACUUM操作,其频率和范围因各站点的需要而异。因此,数据库管理员必须理解这些问题,并制定适当的维护策略。本节集中于解释高层问题;有关命令语法等细节,见VACUUM参考页。

从PostgreSQL 7.2 开始,标准形式的VACUUM可以与正常的数据库操作(选择、插入、更新、删除,但不能修改表定义)并行运行。因此,例行清理不像在以前的版本中那样具有侵入性,也就不那么需要刻意安排在一天中的低使用时段执行。

从PostgreSQL 8.0 开始,有一些配置参数可以调整,用来进一步降低后台清理对性能的影响。参见第 17.4.4 节。

PostgreSQL 8.1 中加入了一种自动执行所需VACUUM操作的机制。参见第 22.1.4 节。

22.1.1. 回收磁盘空间 #

在PostgreSQL的正常操作中,对某一行的UPDATE或DELETE不会立即移除该行的旧版本。这种方法对于获得多版本并发控制(见第 12 章)的好处是必需的:当行版本仍可能对其他事务可见时,就不能删除它。但最终,过时或已删除的行版本将不再是任何事务关心的对象。它占用的空间必须被回收以供新行重用,以避免磁盘空间需求的无限增长。这项工作由运行VACUUM完成。

显然,一个经常被更新或删除的表将需要比很少更新的表更频繁地清理。设置一些只VACUUM选定表的周期性cron任务、跳过那些已知不常更改的表,可能会有用。只有当你同时拥有更新频繁的大型表和不常更新的大型表时,这才可能带来帮助——清理一个小表的额外代价不值得操心。

VACUUM命令有两种变体。第一种形式称为“懒惰清理”或就叫VACUUM,它把表和索引中的过期数据标记为将来可重用;除非过期数据占据的空间位于表的末端并且可以轻松获得一个独占表锁,否则它不会试图回收这些过期数据占用的空间。位于文件开头或中间的未用空间不会导致文件缩短、空间归还给操作系统。VACUUM的这种变体可以与正常的数据库操作并发运行。

第二种形式是VACUUM FULL命令。它使用一种更激进的算法来回收被过期行版本消耗的空间。VACUUM FULL释放的任何空间都会立即归还给操作系统。遗憾的是,VACUUM命令的这种变体在VACUUM FULL处理每个表时会获取该表的独占锁。因此,频繁使用VACUUM FULL可能对并发数据库查询的性能产生极其负面的影响。

标准形式的VACUUM最适合用于维持磁盘空间使用相当平稳的稳态。如果你需要把磁盘空间归还给操作系统,可以使用VACUUM FULL——但释放掉的磁盘空间如果很快又得重新分配,这样做又有什么意义呢?对于维护被频繁更新的表,适度频繁的标准VACUUM运行优于不频繁的VACUUM FULL运行。

对大多数站点,推荐的做法是在一天中低使用时段每天安排一次面向整个数据库的VACUUM,并在必要时更频繁地清理那些更新特别频繁的表。(某些数据修改率极高的安装,甚至会每隔几分钟就对繁忙的表执行一次VACUUM。)如果你在一个集簇中有多个数据库,不要忘记对每一个数据库都执行VACUUM;程序vacuumdb可能会有帮助。

当你知道自己已经删除了一个表中的大多数行时,推荐使用VACUUM FULL,这样用VACUUM FULL更激进的方式可以大幅缩小表的稳态尺寸。在例行的以空间回收为目的的清理中,应使用普通的VACUUM而不是VACUUM FULL。

如果你有一个表,其全部内容会被定期删除,考虑使用TRUNCATE而不是DELETE后接VACUUM。TRUNCATE会立即移除表的全部内容,而不需要后续再执行VACUUM或VACUUM FULL来回收此时未使用的磁盘空间。

22.1.2. 更新规划器统计信息 #

PostgreSQL查询规划器依赖于有关表内容的统计信息,以便为查询生成良好的计划。这些统计信息由ANALYZE命令收集,它既可以单独调用,也可以作为VACUUM的一个可选步骤执行。拥有足够准确的统计信息很重要,否则糟糕的计划选择可能会降低数据库性能。

就像为了回收空间而进行的清理一样,频繁更新统计信息对于更新频繁的表比对于很少更新的表更有用。但即便是更新频繁的表,如果数据的统计分布变化不大,也未必需要更新统计信息。一个简单的经验法则是考虑表中各列的最小值和最大值变化了多少。例如,一个包含行更新时间的timestamp列,会随着行的插入和更新而持续增大其最大值;这样的列可能比例如存放网站页面 URL 的列更需要频繁更新统计信息。URL 列收到更改的频率可能一样高,但其值的统计分布变化大概相对缓慢。

可以对特定表,甚至仅对表中的特定列运行ANALYZE,因此如果你的应用需要,确实可以比其他统计更频繁地更新某些统计信息。然而在实践中,这个特性的用处值得怀疑。从PostgreSQL 7.2 开始,ANALYZE即使在大型表上也是一项相当快的操作,因为它使用对表行的统计随机抽样,而不是读取每一行。因此,定期对整个数据库运行它可能要简单得多。

提示

虽然按列微调ANALYZE的频率未必很有成效,但按列调整ANALYZE收集的统计信息详细程度可能是值得的。在WHERE子句中被频繁使用且数据分布高度不规则的列,可能需要比其他列更细粒度的数据直方图。参见ALTER TABLE SET STATISTICS。

对大多数站点,推荐的做法是在一天中低使用时段每天安排一次面向整个数据库的ANALYZE;这可以方便地与每夜的VACUUM结合进行。不过,表统计信息变化相对缓慢的站点可能会发现这样做过了头,更低频率的ANALYZE运行就足够了。

22.1.3. 防止事务 ID 回卷失败 #

PostgreSQL的 MVCC 事务语义依赖于比较事务 ID(XID)数值的能力:插入 XID 大于当前事务 XID 的行版本处于“未来”,不应对当前事务可见。但事务 ID 的大小有限(撰写本文时为 32 位),长期运行的集簇(超过 40 亿个事务)会遭遇事务 ID 回卷:XID 计数器回卷到零,原本处于过去的事务突然看起来处于未来——这意味着它们的输出变得不可见。简而言之,灾难性的数据丢失。(实际上数据还在那里,但如果你无法访问它,这并没有什么安慰作用。)

在PostgreSQL 7.2 之前,对付 XID 回卷的唯一手段是每至少 40 亿个事务重新执行一次initdb。这对于高流量站点当然不能令人满意,因此设计了一种更好的解决方案。新方法允许服务器无限期运行,而不需要initdb或任何形式的重启。其代价是这样一条维护要求:数据库中的每个表必须每至少 10 亿个事务清理一次。

实践中这并不是一个苛刻的要求,但由于未能满足它的后果可能是彻底的数据丢失(而不只是浪费磁盘空间或性能变慢),因此专门做了一些规定来帮助数据库管理员避免灾难。对于集簇中的每个数据库,PostgreSQL会跟踪上一次数据库范围VACUUM的时间。当任何数据库接近 10 亿事务的危险水平时,系统将开始发出警告消息。如果不采取任何措施,它最终会关闭正常操作,直到完成适当的手工维护。本节其余部分给出具体细节。

对付 XID 比较的新方法区分两个特殊的 XID,即 1 号和 2 号(BootstrapXID和FrozenXID)。这两个 XID 总是被认为比任何正常 XID 都旧。正常 XID(大于 2 的那些)使用模 231算术进行比较。这意味着对每个正常 XID 来说,都有 20 亿个“更旧”的 XID 和 20 亿个“更新”的 XID;换一种说法,正常 XID 空间是环形的,没有端点。因此,一旦一个行版本以某个特定的正常 XID 创建,那么无论我们谈论的是哪个正常 XID,在接下来的 20 亿个事务里,该行版本都会显得处于“过去”。如果行版本在超过 20 亿个事务之后仍然存在,它就会突然显得处于未来。为防止这一点,旧行版本必须在达到 20 亿个事务这个界限之前的某个时刻被重新指派 XIDFrozenXID。一旦被指派了这个特殊的 XID,无论回卷问题如何,它们对所有正常事务来说都处于“过去”,因此这样的行版本在被删除之前一直有效,无论时间多久。这种 XID 的重新指派由VACUUM处理。

VACUUM的常规策略是,把正常 XID 距今超过 10 亿个事务的所有行版本都重新指派为FrozenXID。这一策略会在原始插入 XID 不再可能令人感兴趣之前保留它。(实际上,大多数行版本可能从生到死都从未被“冻结”过。)按照这一策略,任何表上两次VACUUM之间的最大安全间隔正好是 10 亿个事务:如果等待更久,上一次还不够旧、未被重新指派的某个行版本现在可能已超过 20 亿个事务的年龄,从而回卷到了未来——也就是对你而言丢失了。(当然,再经过 20 亿个事务它还会重新出现,但那于事无补。)

由于前述原因,本来就需要周期性地运行VACUUM,因此任何表不太可能长达 10 亿个事务都没有被清理。但为了帮助管理员确保满足这一约束,VACUUM在系统表pg_database中存储事务 ID 统计信息。特别地,任何一次数据库范围的VACUUM操作(即不指定具体表的VACUUM)完成时,都会更新该数据库pg_database行中的datfrozenxid列。该字段中存储的值就是那次VACUUM命令所使用的冻结截止 XID。比该截止 XID 更旧的所有正常 XID 都保证已在该数据库内被替换为FrozenXID。查看这些信息的一个便捷方法是执行查询:

SELECT datname, age(datfrozenxid) FROM pg_database;

age列度量从截止 XID 到当前事务 XID 的事务数。

按照标准的冻结策略,一个刚完成清理的数据库,其age列将从 10 亿开始。当age接近 20 亿时,必须再次对该数据库进行清理,以避免回卷失败的风险。推荐的做法是每个数据库至少每 5 亿个事务执行一次VACUUM,以留出充足的安全余量。为帮助遵守这一规则,任何一次数据库范围的VACUUM都会在存在pg_database条目显示出超过 15 亿事务的age时自动发出警告,例如:

play=# VACUUM;
WARNING:  database "mydb" must be vacuumed within 177009986 transactions
HINT:  To avoid a database shutdown, execute a full-database VACUUM in "mydb".
VACUUM

如果忽略VACUUM发出的这些警告,那么当距回卷只剩不到 1000 万个事务时,PostgreSQL将开始在每次事务启动时发出类似上面的警告。如果这些警告也被忽略,那么当距回卷只剩不到 100 万个事务时,系统将关闭并拒绝执行任何新事务:

play=# select 2+2;
ERROR:  database is shut down to avoid wraparound data loss in database "mydb"
HINT:  Stop the postmaster and use a standalone backend to VACUUM in "mydb".

设置 100 万个事务的安全余量,是为了让管理员能够通过手工执行所需的VACUUM命令在不丢失数据的情况下恢复。然而,由于系统一旦进入安全关闭模式就不再执行命令,唯一的办法是停止 postmaster 并使用独立后端执行VACUUM。独立后端不强制执行关闭模式。关于使用独立后端的细节,见postgres参考页。

带FREEZE选项的VACUUM使用更激进的冻结策略:只要行版本旧到被所有打开的事务都认为是好的,就会被冻结。特别地,在一个除此之外空闲的数据库中执行VACUUM FREEZE,可以保证该数据库中的所有行版本都被冻结。因此,只要该数据库不以任何方式被修改,它就不需要再进行后续的清理来避免事务 ID 回卷问题。这一技术被initdb用来准备template0数据库。它也应当用于准备任何要在pg_database中标记为datallowconn = false的用户创建数据库,因为对于无法连接的数据库,并没有方便的办法执行VACUUM。

警告

在pg_database中被标记为datallowconn = false的数据库被假定为已正确冻结;自动警告和回卷保护关闭不会考虑此类数据库。因此,在把数据库标记为datallowconn = false之前,必须由你自己确保已正确冻结了它。

22.1.4. 自动清理守护进程 #

从PostgreSQL 8.1 开始,有了一个独立的可选服务器进程,称为自动清理守护进程,其目的是自动执行VACUUM和ANALYZE命令。启用后,自动清理守护进程会周期性地运行,检查那些已经积累了大量插入、更新或删除元组的表。这些检查使用行级统计收集功能;因此,除非stats_start_collector和stats_row_level都被设置为true,否则无法使用自动清理守护进程。另外,在选取superuser_reserved_connections的值时,为自动清理进程保留一个连接槽位也很重要。

自动清理守护进程在启用后,每隔autovacuum_naptime秒运行一次,并确定要处理哪个数据库。任何接近事务 ID 回卷的数据库都会被立即处理。在这种情况下,自动清理会发出一次数据库范围的VACUUM调用;如果是一个模板数据库,则发出VACUUM FREEZE,然后终止。如果没有数据库满足这一标准,则选择最久未被自动清理处理过的那个数据库。在这种情况下,会检查所选数据库中的每个表,并根据需要发出各自的VACUUM或ANALYZE命令。

对于每个表,将根据两个条件来决定要应用哪些操作。如果自上次 VACUUM 以来失效的元组数超过“清理阈值”,就会清理该表。清理阈值定义为:

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

其中,清理基础阈值为 autovacuum_vacuum_threshold,清理比例因子为 autovacuum_vacuum_scale_factor,元组数为 pg_class.reltuples。失效元组数来自统计收集器;这是一个近似计数,由每次 UPDATE 和 DELETE 操作更新。(之所以只是近似值,是因为高负载下可能丢失部分信息。)对于分析操作,也使用了一个类似的条件:其阈值定义如下:

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

该阈值会与自上次 ANALYZE 以来插入、更新或删除的元组总数进行比较。

默认阈值和尺度因子取自 postgresql.conf,但也可以通过在系统目录pg_autovacuum中创建条目来按表覆盖它们。如果某个特定表存在 pg_autovacuum 行,则应用该行指定的设置;否则使用全局设置。关于全局设置的更多细节,见第 17.9 节。

除了基础阈值和缩放因子之外,还有三个可以通过 pg_autovacuum 为每个表设置的参数。第一个是 pg_autovacuum.enabled,可以把它设置为false,指示自动清理守护进程完全跳过该特定表。这种情况下,只有在自动清理处理整个数据库时,它才会触及该表。另外两个参数,即清理代价延迟(pg_autovacuum.vac_cost_delay)和清理代价限制(pg_autovacuum.vac_cost_limit),用于为基于代价的清理延迟特性设置表特定的值。

如果 pg_autovacuum 中的任何值被设置为负数,或者某个特定表在 pg_autovacuum 中根本没有对应的行,则使用 postgresql.conf 中相应的值。

目前还不支持以任何方式创建 pg_autovacuum 条目,只能手工向该目录执行 INSERT。这一特性在将来的发行版中会得到改进,而且该目录的定义也很可能会改变。

小心

pg_autovacuum 系统目录的内容目前不会被 pg_dump 和 pg_dumpall 工具保存到数据库转储中。如果你希望在转储/重新装载的循环中保留它们,请务必手工转储该目录。

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。