pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
目录
和任何数据库软件一样,PostgreSQL为了获得最佳性能, 需要定期执行某些任务。这里讨论的任务是必需的, 但它们本质上是重复性的,因此可以很容易地用标准工具实现自动化, 例如 cron 脚本或 Windows 的 Task Scheduler。建立合适的脚本并检查其是否成功执行, 是数据库管理员的职责。
一个显而易见的维护任务,是按固定计划创建数据的备份副本。没有最近的备份, 在灾难(磁盘故障、火灾、误删关键表等)发生后就没有恢复的可能。 PostgreSQL提供的备份和恢复机制在 第 24 章中有详细讨论。
另一大类维护任务是定期对数据库进行“清理”。这一活动在 第 23.1 节中讨论。与之密切相关的是更新查询规划器 将会使用的统计信息,这在第 23.1.2 节中讨论。
另一项可能需要定期关注的任务是日志文件管理。这在 第 23.3 节中讨论。
和某些其他数据库管理系统相比,PostgreSQL的维护工作量较小。 不过,适当地关注这些任务,将大大有助于确保你愉快而高效地使用该系统。
PostgreSQL的VACUUM命令必须定期运行,原因有以下几个:
标准形式的VACUUM可以与生产数据库操作并行运行。SELECT、INSERT、UPDATE和DELETE等命令会继续正常工作,不过在表被清理期间,你将不能使用ALTER TABLE ADD COLUMN等命令修改该表的定义。另外,VACUUM会产生相当大量的 I/O 流量,这可能导致其他活动会话性能变差。有一些配置参数可以调整,用来降低后台清理对性能的影响——参见第 18.4.4 节。
幸运的是,自动清理守护进程会监视表活动并在必要时执行VACUUM。自动清理是动态工作的,因此往往优于管理员调度安排的清理。
在PostgreSQL的正常操作中,对某一行的UPDATE或DELETE不会立即移除该行的旧版本。这种方法对于获得多版本并发控制(见第 13 章)的好处是必需的:当行版本仍可能对其他事务可见时,就不能删除它。但最终,过时或已删除的行版本将不再是任何事务关心的对象。它占用的空间必须被回收以供新行重用,以避免磁盘空间需求的无限增长。这项工作由运行VACUUM完成。
VACUUM命令有两种变体。第一种形式称为“懒惰清理”或就叫VACUUM,它把表和索引中的死数据标记为将来可重用;除非死数据占据的空间位于表的末端并且可以轻松获得一个独占表锁,否则它不会试图回收这些死数据占用的空间。位于文件开头或中间的未用空间不会导致文件缩短、空间归还给操作系统。VACUUM的这种变体可以与正常的数据库操作并发运行。
第二种形式是VACUUM FULL命令。它使用一种更激进的算法来回收被死行版本消耗的空间。VACUUM FULL释放的任何空间都会立即归还给操作系统,并且表数据会在磁盘上被物理地压实。遗憾的是,VACUUM命令的这种变体在VACUUM FULL处理每个表时会获取该表的独占锁。因此,频繁使用VACUUM FULL可能对并发数据库查询的性能产生极其负面的影响。
幸运的是,自动清理守护进程会监视表活动并在必要时执行VACUUM。这样,除了最不寻常的情况之外,管理员无需再为磁盘空间回收操心。
对于想自己控制VACUUM的管理员,标准形式的VACUUM最适合用来维持磁盘空间使用的稳态。如果你需要把磁盘空间归还给操作系统,可以使用VACUUM FULL,但如果该表将来还会再次增长,这样做是不明智的。对于维护被频繁更新的表,适度频繁的标准VACUUM运行优于不频繁的VACUUM FULL运行。不过,如果某些被频繁更新的表由于长时间不经常VACUUM已经膨胀得太厉害,你可以使用VACUUM FULL或CLUSTER来恢复性能(扫描一个几乎全是死行的表会慢得多)。
对于不使用自动清理的人,一种做法是在低使用时段每天安排一次面向整个数据库的VACUUM,并在必要时更频繁地清理那些更新特别频繁的表。(某些更新率极高的安装,甚至会每隔几分钟就清理一次最繁忙的表。)如果你在一个集簇中有多个数据库,不要忘记对每一个数据库都执行VACUUM;程序vacuumdb可能会有帮助。
当你知道自己已经删除了一个表中的大多数行时,推荐使用VACUUM FULL,这样用VACUUM FULL更激进的方式可以大幅缩小表的稳态尺寸。在例行的以空间回收为目的的清理中,应使用普通的VACUUM而不是VACUUM FULL。
如果你有一个表,其全部内容会被定期删除,考虑使用TRUNCATE而不是DELETE后接VACUUM。TRUNCATE会立即移除表的全部内容,而不需要后续再执行VACUUM或VACUUM FULL来回收此时未使用的磁盘空间。
PostgreSQL查询规划器依赖于有关表内容的统计信息,以便为查询生成良好的计划。这些统计信息由ANALYZE命令收集,它既可以单独调用,也可以作为VACUUM的一个可选步骤执行。拥有足够准确的统计信息很重要,否则糟糕的计划选择可能会降低数据库性能。
就像为了回收空间而进行的清理一样,频繁更新统计信息对于更新频繁的表比对于很少更新的表更有用。但即便是更新频繁的表,如果数据的统计分布变化不大,也未必需要更新统计信息。一个简单的经验法则是考虑表中各列的最小值和最大值变化了多少。例如,一个包含行更新时间的timestamp列,会随着行的插入和更新而持续增大其最大值;这样的列可能比例如存放网站页面 URL 的列更需要频繁更新统计信息。URL 列收到更改的频率可能一样高,但其值的统计分布变化大概相对缓慢。
可以对特定表,甚至仅对表中的特定列运行ANALYZE,因此如果你的应用需要,确实可以比其他统计更频繁地更新某些统计信息。然而在实践中,通常最好直接分析整个数据库,因为这是一项很快的操作。它使用对表行的统计随机抽样,而不是读取每一行。
虽然按列微调ANALYZE的频率未必很有成效,但按列调整ANALYZE收集的统计信息详细程度可能是值得的。在WHERE子句中被频繁使用且数据分布高度不规则的列,可能需要比其他列更细粒度的数据直方图。参见ALTER TABLE SET STATISTICS。
幸运的是,自动清理守护进程会监视表活动并在必要时执行ANALYZE。这样,管理员就无需手工调度ANALYZE。
对于不使用自动清理的人,一种做法是在一天中低使用时段每天安排一次面向整个数据库的ANALYZE;这可以方便地与每夜的VACUUM结合进行。不过,表统计信息变化相对缓慢的站点可能会发现这样做过了头,更低频率的ANALYZE运行就足够了。
PostgreSQL的 MVCC 事务语义依赖于比较事务 ID(XID)数值的能力:插入 XID 大于当前事务 XID 的行版本处于“未来”,不应对当前事务可见。但事务 ID 的大小有限(撰写本文时为 32 位),长期运行的集簇(超过 40 亿个事务)会遭遇事务 ID 回卷:XID 计数器回卷到零,原本处于过去的事务突然看起来处于未来——这意味着它们的输出变得不可见。简而言之,灾难性的数据丢失。(实际上数据还在那里,但如果你无法访问它,这并没有什么安慰作用。)为避免这种情况,必须每 20 亿个事务至少对每个数据库中的每个表清理一次。
定期清理之所以能解决这个问题,是因为PostgreSQL保留了一个特殊的 XID,即FrozenXID。这个 XID 总是被认为比任何正常 XID 都旧。正常 XID 使用模 231算术进行比较。这意味着对每个正常 XID 来说,都有 20 亿个“更旧”的 XID 和 20 亿个“更新”的 XID;换一种说法,正常 XID 空间是环形的,没有端点。因此,一旦一个行版本以某个特定的正常 XID 创建,那么无论我们谈论的是哪个正常 XID,在接下来的 20 亿个事务里,该行版本都会显得处于“过去”。如果行版本在超过 20 亿个事务之后仍然存在,它就会突然显得处于未来。为防止这一点,旧行版本必须在达到 20 亿个事务这个界限之前的某个时刻被重新指派 XIDFrozenXID。一旦被指派了这个特殊的 XID,无论回卷问题如何,它们对所有正常事务来说都处于“过去”,因此这样的行版本在被删除之前一直有效,无论时间多久。旧 XID 的这种重新指派由VACUUM处理。
VACUUM的行为由配置参数vacuum_freeze_min_age控制:任何比vacuum_freeze_min_age个事务更旧的 XID 都会被替换为FrozenXID。vacuum_freeze_min_age的值较大可以更久地保留事务信息,而值较小则可以增加在表必须再次清理之前可经过的事务数。
一个表可以不清理的最长时间,是 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 来做到。
增大 autovacuum_freeze_max_age 唯一的缺点,是数据库集簇的 pg_clog 子目录会占用更多空间,因为必须保存追溯到 autovacuum_freeze_max_age 视界的所有事务的提交状态。每个事务的提交状态占用两位,因此,如果将 autovacuum_freeze_max_age 设为允许的最大值 20 亿,pg_clog 预计会增长到约 0.5GB。如果这与数据库总大小相比微不足道,建议将 autovacuum_freeze_max_age 设为允许的最大值。否则,应根据愿意为 pg_clog 分配的存储空间来设置。(默认值为 2 亿个事务,对应约 50MB 的 pg_clog 存储。)
降低vacuum_freeze_min_age的一个缺点是,如果行在冻结后不久就被修改(使其获得新的 XID),那么VACUUM把表行的 XID 替换为FrozenXID就是白费时间。因此该设置应该足够大,使行直到不太可能再改变时才被冻结。降低这个设置的另一个缺点是,关于究竟是哪个事务插入或修改了某一行的细节会更早丢失。这些信息有时很有用,尤其是在试图分析数据库故障之后哪里出了问题的时候。出于这两个原因,除非是完全静态的表,否则不建议降低此设置。
为了跟踪数据库中最旧 XID 的年龄,VACUUM在系统表pg_class和pg_database中存储 XID 统计信息。特别地,表的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刚结束后,age(relfrozenxid)应当比所用的vacuum_freeze_min_age设置略大(多出的部分等于自VACUUM开始以来已启动的事务数)。如果age(relfrozenxid)超过了autovacuum_freeze_max_age,该表很快就会被强制执行一次自动清理。
如果自动清理由于某种原因未能清除表中的旧 XID,当数据库最旧的 XID 距回卷点达到一千万个事务时,系统将开始发出如下警告消息:
WARNING: database "mydb" must be vacuumed within 177009986 transactions HINT: To avoid a database shutdown, execute a full-database VACUUM in "mydb".
如果忽略这些警告,当距回卷只剩不到 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参考页。
从PostgreSQL 8.1 开始,有了一个可选的特性,称为自动清理,其目的是自动执行VACUUM和ANALYZE 命令。启用后,自动清理会检查那些已经积累了大量插入、更新或删除元组的表。这些检查依赖于统计收集功能;因此,除非track_counts被设置为true,否则无法使用自动清理。在默认配置下,自动清理是启用的,并且相关配置参数也设置得比较合适。
从PostgreSQL 8.3 开始,自动清理采用了多进程架构:有一个守护进程,称为自动清理启动器,负责为所有数据库启动自动清理工作进程。启动器会把工作分摊到各时间段,但尝试每autovacuum_naptime秒在每个数据库中启动一个工作进程。每个数据库都会启动一个工作进程,同一时刻最多允许autovacuum_max_workers个进程运行。如果要处理的数据库多于autovacuum_max_workers个,第一个工作进程一结束,就会处理下一个数据库。工作进程将检查其数据库中的每个表,并根据需要执行VACUUM和/或ANALYZE。
autovacuum_max_workers设置限制了任一时刻可以运行的工作进程数量。 如果多个大型表在很短时间内都变得需要清理,那么所有自动清理工作进程都可能会长期忙于清理这些表。 这会导致其他表和数据库在某个工作进程空闲下来之前都得不到清理。单个数据库中的工作进程数量没有上限, 但工作进程会尽量避免重复其他工作进程已经完成的工作。注意,正在运行的工作进程数量不计入 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。失效元组数来自统计收集器;这是一个近似计数,由每次 UPDATE 和 DELETE 操作更新。(之所以只是近似值,是因为高负载下可能丢失部分信息。)对于分析操作,也使用了一个类似的条件:其阈值定义如下:
analyze threshold = analyze base threshold + analyze scale factor * number of tuples
该阈值会与自上次 ANALYZE 以来插入或更新的元组总数进行比较。
默认阈值和尺度因子取自 postgresql.conf,但也可以通过在系统目录pg_autovacuum中创建条目来按表覆盖它们。如果某个特定表存在 pg_autovacuum 行,则应用该行指定的设置;否则使用全局设置。关于全局设置的更多细节,见第 18.9 节。
除了基础阈值和缩放因子之外,还有五个可以通过 pg_autovacuum 为每个表设置的参数。第一个是 pg_autovacuum.enabled,可以把它设置为false,指示自动清理守护进程完全跳过该特定表。这种情况下,只有在必须防止事务 ID 回卷时,自动清理才会触及该表。接下来两个参数,即清理代价延迟(pg_autovacuum.vac_cost_delay)和清理代价限制(pg_autovacuum.vac_cost_limit),用于为基于代价的清理延迟特性设置表特定的值。最后两个参数(pg_autovacuum.freeze_min_age)和(pg_autovacuum.freeze_max_age),分别用于为vacuum_freeze_min_age和autovacuum_freeze_max_age设置表特定的值。
如果 pg_autovacuum 中的任何值被设置为负数,或者某个特定表在 pg_autovacuum 中根本没有对应的行,则使用 postgresql.conf 中相应的值。
目前还不支持以任何方式创建 pg_autovacuum 条目,只能手工向该目录执行 INSERT。这一特性在将来的发行版中会得到改进,而且该目录的定义也很可能会改变。
pg_autovacuum 系统目录的内容目前不会被 pg_dump 和 pg_dumpall 工具保存到数据库转储中。如果你希望在转储/重新装载的循环中保留它们,请务必手工转储该目录。
当多个工作进程同时运行时,代价限制会在所有运行中的工作进程之间 “平衡”,这样无论实际运行了多少个工作进程,对系统的总体影响都相同。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。