pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
为了保持PostgreSQL服务器平稳运行, 有一些必须定期执行的例行维护事务。这里讨论的任务本质上是 重复性的,使用诸如cron脚本之类的 标准 Unix 工具就可以轻松自动化。但是,建立合适的脚本并检查 它们是否成功执行是数据库管理员的职责。
一项显而易见的维护任务是按固定的计划定期创建数据的备份 副本。如果没有较新的备份,在灾难(磁盘故障、火灾、误删除 关键表等)发生之后你就没有任何恢复的机会。 PostgreSQL中可用的备份和恢复机制 在第 22 章中有详细讨论。
另一类主要的维护任务是对数据库进行周期性的“清理” (vacuuming)。这项活动在第 21.1 节中讨论。
还有一件可能需要周期性关注的事情是日志文件管理。这在 第 21.3 节中讨论。
与其他一些数据库管理系统相比,PostgreSQL 的维护需求较低。尽管如此,对这些任务给予适当的关注,将大大 有助于确保你愉快而高效地使用这个系统。
PostgreSQL的VACUUM命令 必须定期运行,原因有以下几点:
出于这些原因而执行的VACUUM操作的频率和范围, 因各站点的需求而异。因此,数据库管理员必须理解这些问题并 制定合适的维护策略。本节集中解释高层面的问题;关于命令语法 等细节,请参阅VACUUM命令参考页。
从PostgreSQL 7.2 开始,标准形式的 VACUUM可以与正常的数据库操作(选择、插入、更新、 删除,但不包括对表定义的更改)并行运行。因此,例行清理不像 以前的版本那样具有侵入性,也就不必那么刻意地安排在一天中 使用率低的时段进行。
在PostgreSQL的正常操作中,对一行 执行UPDATE或DELETE并不会立即删除该行 的旧版本。为了获得多版本并发控制的好处(见第 12 章), 这种做法是必要的:在其他事务仍可能看到某个行版本时,不能 删除它。但最终,一个过期或已删除的行版本不再被任何事务关注。 它占用的空间必须被回收以供新行重用,以避免磁盘空间需求 无限增长。这项工作通过运行VACUUM完成。
显然,频繁更新或删除的表需要比很少更新的表更经常地清理。 设置只清理选定表的周期性cron任务可能有用, 跳过已知不常变化的表。只有当你同时有大型的频繁更新表和 大型的不常更新表时,这才可能有帮助——清理一个小表的额外 开销不值得担心。
标准形式的VACUUM最适合以维持磁盘空间的平稳占用 为目标。标准形式会找到旧的行版本并使其空间可在表内重用, 但它并不十分努力地缩短表文件并把磁盘空间归还给操作系统。 如果你需要把磁盘空间归还给操作系统,可以使用 VACUUM FULL——但释放出的磁盘空间如果很快又 要重新分配,这样做又有什么意义呢?对于维护频繁更新的表, 较频繁的标准VACUUM运行比不频繁的 VACUUM FULL运行是更好的做法。
对大多数站点,推荐的做法是每天在使用率低的时段安排一次 数据库范围的VACUUM,必要时辅以对频繁更新表的 更频繁清理。(如果集群中有多个数据库,别忘了对每个数据库 都进行清理;vacuumdb程序可能会有帮助。) 例行的空间回收清理应使用普通的VACUUM,而不是 VACUUM FULL。
在你确知已删除了一个表中大部分行的情况下,推荐使用 VACUUM FULL,这样表的稳态尺寸可以通过 VACUUM FULL更激进的方式大幅收缩。
如果你有一个表的内容会周期性地被完全删除,可以考虑用 TRUNCATE来完成,而不是DELETE后跟 VACUUM。
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 亿个事务这一时限之前的某个时刻,把它们的 XID 重新 指定为FrozenXID。一旦被指定了这个特殊的 XID, 无论回卷问题如何,它们对所有普通事务都会显得处于“过去”, 因此这样的行版本在被删除之前一直有效,不管时间多长。这种 XID 的重新指定由VACUUM处理。
VACUUM的常规策略是把FrozenXID重新 指定给任何普通 XID 已在过去中超过 10 亿个事务的行版本。这个 策略保留了原始的插入 XID,直到它不再可能被关注。(事实上, 大多数行版本可能终生都不会被“冻结”。)按照这个策略, 任何表上两次VACUUM运行之间的最大安全间隔正好是 10 亿个事务:如果你等得更久,某个上次还不够旧、未被重新指定 的行版本现在可能已超过 20 亿个事务并回卷到了未来——也就是 说,对你而言丢失了。(当然,再过 20 亿个事务后它会重新出现, 但那无济于事。)
由于前述原因本来就需要周期性地运行VACUUM,任何 一个表长达 10 亿个事务未被清理的情况不太可能发生。但为了 帮助管理员确保满足这个约束,VACUUM把事务 ID 统计 信息存储在系统表pg_database中。特别地,在完成 任何数据库范围的清理操作(即不指定具体表的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都会在存在 pg_database项显示age超过 15 亿个事务时 自动发出警告,例如:
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关于未清理数据库的自动警告消息将 忽略datallowconn = false的 pg_database项,以避免对这些数据库发出虚假警告; 因此,确保这样的数据库被正确冻结是你的责任。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。