pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
为了让PostgreSQL服务器平稳运行,需要定期执行一些 例行的维护工作。这里讨论的任务本质上是重复性的,可以很容易地用标准 Unix 工具 实现自动化,例如 cron 脚本。但建立合适的脚本并 检查它们是否成功执行,是数据库管理员的职责。
一个显而易见的维护任务,是按固定计划创建数据的备份副本。没有最近的备份, 在灾难(磁盘故障、火灾、误删关键表等)发生后就没有恢复的可能。 PostgreSQL提供的备份和恢复机制在 第 22 章中有详细讨论。
另一大类维护任务是定期对数据库进行“清理”。这一活动在 第 21.1 节中讨论。
另一件可能需要定期关注的事情是日志文件管理。这在 第 21.3 节中讨论。
和某些其他数据库管理系统相比,PostgreSQL的维护工作量较小。 不过,适当地关注这些任务,将大大有助于确保你愉快而高效地使用该系统。
PostgreSQL的VACUUM命令必须定期运行,原因有以下几个:
出于上述各原因而执行的VACUUM操作,其频率和范围因各站点的需要而异。因此,数据库管理员必须理解这些问题,并制定适当的维护策略。本节集中于解释高层问题;有关命令语法等细节,见VACUUM参考页。
从PostgreSQL 7.2 开始,标准形式的VACUUM 可以与正常的数据库操作(选择、插入、更新、删除,但不包括对表定义的更改)并行运行。 因此,例行的清理不像在以前的版本中那样具有干扰性,也就不必那么刻意地把它安排在 一天中的低使用时段进行。
从PostgreSQL 8.0 开始,有一些配置参数可以调整,用来进一步 降低后台清理对性能的影响。参见 第 16.4.3.4 节。
在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可能会有帮助。
contrib/pg_autovacuum程序可用于自动化高频的清理操作。
当你知道自己已经删除了一个表中的大多数行时,推荐使用VACUUM FULL,这样用VACUUM FULL更激进的方式可以大幅缩小表的稳态尺寸。在例行的以空间回收为目的的清理中,应使用普通的VACUUM而不是VACUUM FULL。
如果你有一个表,其内容会被定期删除,考虑使用TRUNCATE而不是DELETE后接VACUUM。TRUNCATE会立即移除表的全部内容,而不需要后续再执行VACUUM或VACUUM FULL来回收此时未使用的磁盘空间。
PostgreSQL查询规划器依赖于有关表内容的统计信息,以便为查询生成良好的计划。这些统计信息由ANALYZE命令收集,它既可以单独调用,也可以作为VACUUM的一个可选步骤执行。拥有足够准确的统计信息很重要,否则糟糕的计划选择可能会降低数据库性能。
就像为了回收空间而进行的清理一样,频繁更新统计信息对于更新频繁的表比对于很少更新的表更有用。但即便是更新频繁的表,如果数据的统计分布变化不大,也未必需要更新统计信息。一个简单的经验法则是考虑表中各列的最小值和最大值变化了多少。例如,一个包含行更新时间的timestamp列,会随着行的插入和更新而持续增大其最大值;这样的列可能比例如存放网站页面 URL 的列更需要频繁更新统计信息。URL 列收到更改的频率可能一样高,但其值的统计分布变化大概相对缓慢。
可以对特定表,甚至仅对表中的特定列运行ANALYZE,因此如果你的应用需要,确实可以比其他统计更频繁地更新某些统计信息。然而在实践中,此特性的用处令人怀疑。从PostgreSQL 7.2 开始,ANALYZE即使对大型表也是一项相当快的操作,因为它使用对表行的统计随机抽样,而不是读取每一行。所以,每隔一段时间就对整个数据库运行一遍它,可能要简单得多。
虽然按列微调ANALYZE的频率未必很有成效,但按列调整ANALYZE收集的统计信息详细程度可能是值得的。在WHERE子句中被频繁使用且数据分布高度不规则的列,可能需要比其他列更细粒度的数据直方图。参见ALTER TABLE SET STATISTICS。
对大多数站点,推荐的做法是在一天中低使用时段每天安排一次面向整个数据库的ANALYZE;这可以方便地与每夜的VACUUM结合进行。不过,表统计信息变化相对缓慢的站点可能会发现这样做过了头,更低频率的ANALYZE运行就足够了。
PostgreSQL的 MVCC 事务语义依赖于比较事务 ID(XID)数值的能力:插入 XID 大于当前事务 XID 的行版本处于“未来”,不应对当前事务可见。但事务 ID 的大小有限(撰写本文时为 32 位),长期运行的集簇(超过 40 亿个事务)会遭遇事务 ID 回卷:XID 计数器回卷到零,原本处于过去的事务突然看起来处于未来——这意味着它们的输出变得不可见。简而言之,灾难性的数据丢失。(实际上数据还在那里,但如果你无法访问它,这并没有什么安慰作用。)
在PostgreSQL 7.2 之前,对抗 XID 回卷的唯一防御手段, 是至少每 40 亿个事务重新执行一次initdb。这对高流量站点当然 不是很令人满意,因此人们设计出了更好的解决方案。新方法允许服务器无限期地保持 运行,而不需要initdb或任何形式的重启。其代价是这一维护要求: 数据库中的每个表必须每 10 亿个事务至少清理一次。
在实践中这并不是一个苛刻的要求,但由于不满足它的后果可能是彻底的数据丢失 (而不只是浪费磁盘空间或性能变慢),系统提供了一些特殊的手段,帮助数据库管理员 跟踪自上一次VACUUM以来的时间。本节余下部分给出细节。
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 统计信息。特别地,某个数据库的pg_database行中的 datfrozenxid列,会在任何数据库范围的VACUUM 操作(即不指定具体表名的VACUUM)完成时被更新。该字段中存储的值 是那次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: some databases have not been vacuumed in 1613770184 transactions HINT: Better vacuum them within 533713463 transactions, or you may have a wraparound failure. VACUUM
带FREEZE选项的VACUUM使用一种更激进的冻结策略: 只要行版本旧到能被所有打开的事务认为是好的,就会被冻结。特别地,如果在一个此外 处于空闲状态的数据库中执行VACUUM FREEZE,则保证该数据库中的 所有行版本都会被冻结。因此,只要该数据库不发生任何方式的修改, 就不需要再进行后续清理来避免事务 ID 回卷问题。这一技术被initdb 用来准备template0数据库。在准备任何要被标记为 pg_database中datallowconn = false 的用户创建数据库时,也应当使用它,因为对于一个你无法连接的数据库,没有任何方便的 办法对它执行VACUUM。注意,VACUUM关于未清理 数据库的自动警告消息将忽略datallowconn = false 的pg_database条目,以避免就这些数据库发出虚假警告;因此, 确保此类数据库被正确冻结是你自己的责任。
为确保对事务回卷的安全性,必须对每个数据库中的 每个表(包括系统目录)每 10 亿个事务至少清理一次。 我们见过因人们认定只需清理其活跃的用户表、而不是发出数据库范围的清理命令 而造成的数据丢失局面。那样做在一段时间内看起来一切正常……仅此而已。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。