选择 打开 改范围 完整检索页

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
历史版本PostgreSQL 9.0 已于 2015 年 10 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本

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

和任何数据库软件一样,PostgreSQL为了获得最佳性能, 需要定期执行某些任务。这里讨论的任务是必需的, 但它们本质上是重复性的,因此可以很容易地用标准工具实现自动化, 例如 cron 脚本或 Windows 的 Task Scheduler。建立合适的脚本并检查其是否成功执行, 是数据库管理员的职责。

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

另一大类维护任务是定期对数据库进行清理。这一活动在 第 23.1 节中讨论。与之密切相关的是更新查询规划器 将会使用的统计信息,这在第 23.1.3 节中讨论。

另一项可能需要定期关注的任务是日志文件管理。这在 第 23.3 节中讨论。

check_postgres可用于监控数据库健康状况并报告异常情况。check_postgres能与 Nagios 和 MRTG 集成,但也可以单独运行。

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

23.1. 日常清理 #

PostgreSQL数据库需要一种称为清理的 周期性维护。对于许多安装,让第 23.1.5 节所述的 自动清理守护进程执行清理就足够了。为了在你的场景中获得最佳效果, 你可能需要调整其中描述的自动清理参数。有些数据库管理员希望用手工管理的 VACUUM命令来补充甚至取代该守护进程的工作,这类命令通常由 cronTask Scheduler 脚本按计划执行。要正确设置手工管理的清理,理解下面几小节讨论的问题至关重要。 依赖自动清理的管理员也不妨略读这一材料,以帮助理解和调整自动清理。

23.1.1. 清理基础 #

PostgreSQLVACUUM命令必须定期处理每个表,原因如下:

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

这些原因分别要求以不同的频率和范围执行VACUUM操作,下面几小节将对此说明。

VACUUM有两种变体:标准的 VACUUMVACUUM FULLVACUUM FULL可以回收更多磁盘空间, 但执行速度要慢得多。此外,标准形式的 VACUUM 可以与生产数据库操作并行运行。 (SELECTINSERTUPDATE 以及 DELETE 等命令会继续正常工作,不过在表被清理时,你将不能使用 ALTER TABLE 等命令修改该表的定义。) VACUUM FULL 需要对正在处理的表持有独占锁,因此不能与对该表的其他使用并行进行。 一般来说,管理员应尽量使用标准 VACUUM 并避免 VACUUM FULL

VACUUM会产生大量 I/O 流量,这可能导致其他活动会话性能变差。 可以调整一些配置参数来降低后台清理对性能的影响,参见 第 18.4.3 节

23.1.2. 回收磁盘空间 #

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

标准形式的 VACUUM 会移除表和索引中的死行版本,并将空间标记为可供将来重用。 不过,它不会把空间归还给操作系统,除非出现一种特殊情况:表尾的一个或多个页面完全空闲, 并且能够轻松获得一个排他表锁。相比之下,VACUUM FULL 会主动压实表, 它会写出一个不含死空间的全新表文件版本。这会将表的大小降到最小,但可能耗时很长。 在操作完成之前,它还需要额外的磁盘空间来存放表的新副本。

例行清理的通常目标,是足够频繁地执行标准 VACUUM, 从而避免需要 VACUUM FULL。自动清理守护进程就是按照这种方式工作的, 事实上它永远不会发出 VACUUM FULL。这种方法的思路不是让表始终保持在最小尺寸, 而是让磁盘空间的使用维持在稳态:每个表占用的空间相当于其最小尺寸,再加上两次清理之间又被用掉的空间。 虽然 VACUUM FULL 可以把表重新缩小到最小尺寸,并把磁盘空间归还给操作系统, 但如果该表之后还会再次增长,这样做意义并不大。因此,对于维护被频繁更新的表, 与其不常执行 VACUUM FULL,不如适度频繁地执行标准 VACUUM

有些管理员喜欢自己安排清理,例如在夜间负载较低时完成全部工作。按固定时间表执行清理的难点在于, 如果某个表的更新活动出现意外高峰,它可能膨胀到确实需要 VACUUM FULL 才能回收空间的程度。使用自动清理守护进程可以缓解这个问题,因为守护进程会根据更新活动动态安排清理。 除非工作负载极其可预测,否则完全禁用守护进程是不明智的。一种可能的折中办法是设置守护进程参数, 使其只对异常繁重的更新活动作出反应,从而防止情况失控,而在负载正常时,则期望定期调度的 VACUUM 完成大部分工作。

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

提示

当一个表由于大规模更新或删除活动而包含大量死行版本时,普通的 VACUUM 可能并不能令人满意。如果你有这样一个表,并且需要回收它所占用的多余磁盘空间, 就需要使用 VACUUM FULL,或者改用 CLUSTER, 又或者使用 ALTER TABLE 的某一种重写表变体。这些命令会重写整个表的新副本,并为其构建新的索引。 所有这些选项都需要独占锁。还要注意, 它们会临时额外占用大约等于该表大小的磁盘空间,因为在新表和新索引完成之前, 旧的表和索引副本都不能被释放。

提示

如果你有一个表,其全部内容会被定期删除,考虑使用 TRUNCATE, 而不是先 DELETEVACUUMTRUNCATE 会立即移除表的全部内容,而不需要后续再执行 VACUUMVACUUM FULL 来回收此时未使用的磁盘空间。 缺点是它会破坏严格的 MVCC 语义。

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

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

如果启用了自动清理守护进程,它会在表内容发生足够大变化时自动发出 ANALYZE 命令。不过,管理员也可能更愿意依靠手工调度的 ANALYZE 操作,尤其是在已知某个表上的更新活动不会影响 重要列统计信息的情况下。守护进程严格依据插入或更新的行数来安排 ANALYZE;它并不知道这些变化是否会导致有意义的统计变化。

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

可以对特定表,甚至仅对表中的特定列运行 ANALYZE, 因此如果你的应用需要,确实可以比其他统计更频繁地更新某些统计信息。 然而在实践中,通常最好直接分析整个数据库,因为这是一项很快的操作。 ANALYZE 使用对表行的统计随机抽样,而不是读取每一行。

提示

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

此外,默认情况下,关于函数选择率的信息很有限。不过,如果创建了使用函数调用的表达式索引,系统就会收集关于该函数的有用统计信息,这可以显著改善使用该表达式索引的查询计划。

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

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

定期清理之所以能解决这个问题,是因为PostgreSQL保留了一个特殊的 XID,即FrozenXID。这个 XID 不遵循正常的 XID 比较规则,它总是被认为比任何正常 XID 都旧。正常 XID 使用模 232算术进行比较。这意味着对每个正常 XID 来说,都有 20 亿个更旧的 XID 和 20 亿个更新的 XID;换一种说法,正常 XID 空间是环形的,没有端点。因此,只要行版本的 XID 在其创建之后的接下来 20 亿个事务内被替换为FrozenXID,无论我们谈论的是哪个正常 XID,都不会有错误。如果行版本在超过 20 亿个事务之后仍然存在,它就会突然显得处于未来。为防止这一点,旧行版本必须在达到 20 亿个事务这个界限之前的某个时刻被重新指派 XIDFrozenXID。一旦被指派了这个特殊的 XID,无论回卷问题如何,它们对所有正常事务来说都处于过去,因此这样的行版本在被删除之前一直有效,无论时间多久。旧 XID 的这种重新指派由VACUUM处理。

vacuum_freeze_min_age控制 XID 值必须有多旧才会被替换为FrozenXID。该设置值较大可以更久地保留事务信息,而值较小则可以增加在表必须再次清理之前可经过的事务数。

VACUUM通常会跳过没有死行版本的页面,但这些页面中可能仍有带旧 XID 值的行版本。要确保所有旧 XID 都已被替换为FrozenXID,就需要对整个表进行一次扫描。vacuum_freeze_table_age控制VACUUM何时执行这种操作:如果该表在vacuum_freeze_table_age减去vacuum_freeze_min_age个事务内未曾被完整扫描,就会强制进行一次全表扫描。把它设置为 0 会强制VACUUM总是扫描所有页面,实际上忽略了可见性映射。

一个表可以不清理的最长时间,是 20 亿个事务减去VACUUM上次扫描全表时的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 来做到。

vacuum_freeze_table_age 的实际最大值是 0.95 * autovacuum_freeze_max_age;高于该值的设置会被截断到该最大值。 设置一个高于 autovacuum_freeze_max_age 的值没有意义,因为无论如何, 防回卷自动清理都会在那时被触发,而 0.95 的乘数是为了在那之前留出一些余地以手工运行一次 VACUUM。经验上,vacuum_freeze_table_age 应设置为略低于 autovacuum_freeze_max_age 的值,并留出足够空档, 使一次常规调度的 VACUUM 或由正常删除、更新活动触发的自动清理 能在这个窗口内运行。若设置得过于接近,即使该表最近已经为了回收空间而清理过, 也可能仍会触发防回卷自动清理;而较低的值则会导致更频繁的全表扫描。

增大 autovacuum_freeze_max_age(以及相应的 vacuum_freeze_table_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_classpg_database中存储 XID 统计信息。特别地,表的pg_class行中的relfrozenxid列包含该表上一次全表VACUUM所使用的冻结截止 XID。比这个截止 XID 更旧的所有正常 XID 都保证已在该表内被替换为FrozenXID。类似地,数据库的pg_database行中的datfrozenxid列是该数据库中出现的正常 XID 的下界——它就是该数据库内各表relfrozenxid值的最小值。查看这些信息的一个便捷方法是执行如下查询:

SELECT c.oid::regclass as table_name,
       greatest(age(c.relfrozenxid),age(t.relfrozenxid)) as age
FROM pg_class c
LEFT JOIN pg_class t ON c.reltoastrelid = t.oid
WHERE c.relkind = 'r';

SELECT datname, age(datfrozenxid) FROM pg_database;

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

VACUUM通常只扫描自上次清理以来被修改过的页面,但只有当扫描整个表时, relfrozenxid 才会被推进。当 relfrozenxid的年龄超过vacuum_freeze_table_age个事务、使用了 VACUUMFREEZE 选项,或者所有页面碰巧都需要清理以移除死行版本时, 就会扫描整个表。当 VACUUM 扫描整个表时, 它应把 age(relfrozenxid) 设为略高于所用 vacuum_freeze_min_age 设置的值 (更高的部分等于自 VACUUM 开始以来已启动的事务数)。如果直到达到 autovacuum_freeze_max_age 之前,该表都没有执行一次全表扫描的 VACUUM, 那么很快就会被强制执行一次自动清理。

如果自动清理由于某种原因未能清除表中的旧 XID,当数据库最旧的 XID 距回卷点达到一千万个事务时,系统将开始发出如下警告消息:

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. 自动清理守护进程 #

PostgreSQL 具有一个可选但强烈推荐的特性, 称为自动清理,其目的是自动执行 VACUUMANALYZE 命令。 启用后,自动清理会检查那些已经积累了大量插入、更新或删除元组的表。 这些检查依赖于统计收集功能;因此,除非 track_counts 被设置为 true, 否则无法使用自动清理。在默认配置下,自动清理是启用的,并且相关配置参数也设置得比较合适。

自动清理守护进程实际上由多个进程组成。有一个常驻的守护进程,称为自动清理启动器,负责为所有数据库启动自动清理工作进程。启动器会把工作分摊到各时间段,尝试每autovacuum_naptime秒在每个数据库中启动一个工作进程。(因此,如果安装有N个数据库,每autovacuum_naptime/N秒就会启动一个新的工作进程。)同一时刻最多允许autovacuum_max_workers个工作进程运行。如果要处理的数据库多于autovacuum_max_workers个,第一个工作进程一结束,就会处理下一个数据库。每个工作进程将检查其数据库中的每个表,并根据需要执行VACUUM和/或ANALYZE

如果多个大型表在很短时间内都变得需要清理,那么所有自动清理工作进程都可能会长期忙于清理这些表。 这会导致其他表和数据库在某个工作进程空闲下来之前都得不到清理。单个数据库中的工作进程数量没有上限, 但工作进程会尽量避免重复其他工作进程已经完成的工作。注意,正在运行的工作进程数量不计入 max_connectionssuperuser_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。失效元组数来自统计收集器;这是一个近似计数,由每次 UPDATEDELETE 操作更新。(之所以只是近似值,是因为高负载下可能丢失部分信息。)如果表的 relfrozenxid 值的年龄超过 vacuum_freeze_table_age 个事务,就会扫描整个表以冻结旧元组并推进 relfrozenxid;否则,只扫描自上次清理以来被修改过的页面。

对于分析操作,也使用了一个类似的条件:其阈值定义如下:

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

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

自动清理无法访问临时表。因此,应通过会话 SQL 命令执行适当的清理和分析操作。

默认阈值和尺度因子取自 postgresql.conf,但也可以按表覆盖它们;详情见 存储参数。如果某个设置已经通过存储参数修改, 则使用该值;否则使用全局设置。关于全局设置的更多细节,见 第 18.9 节

除了基础阈值和缩放因子之外,还有六个可以通过存储参数为每个表设置的自动清理参数。第一个参数autovacuum_enabled可以设置为false,指示自动清理守护进程完全跳过该特定表。这种情况下,只有在必须防止事务 ID 回卷时,自动清理才会触及该表。 Another two parameters, autovacuum_vacuum_cost_delay and autovacuum_vacuum_cost_limit, are used to set 用于为基于代价的清理延迟特性设置表特定的值(见第 18.4.3 节)。 autovacuum_freeze_min_age, autovacuum_freeze_max_age and autovacuum_freeze_table_age are used to set values for vacuum_freeze_min_age, autovacuum_freeze_max_age and vacuum_freeze_table_age respectively.

当多个工作进程同时运行时,代价延迟参数会在所有运行中的工作进程之间 平衡,这样无论实际运行了多少个工作进程,系统承受的总 I/O 影响都相同。 不过,任何正在处理那些为每表存储参数 autovacuum_vacuum_cost_delayautovacuum_vacuum_cost_limit 显式设置过值的表的工作进程, 都不会被纳入这个均衡算法。

提交更正

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