↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发中的版本: 19 / 20devel
已结束支持的版本: 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 9.1 已结束支持。 2016-10-27. 请参阅 当前版本手册.

第 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命令来补充甚至取代该守护进程的工作,这类命令通常由 cron 或 Task Scheduler 脚本按计划执行。要正确设置手工管理的清理,理解下面几小节讨论的问题至关重要。依赖自动清理的管理员也不妨略读这一材料,以帮助理解和调整自动清理。

23.1.1. 清理基础 #

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

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

针对上述不同目的,需要以不同的频率和范围执行 VACUUM 操作,下面几节会详细说明。

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

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

23.1.2. 回收磁盘空间 #

在 PostgreSQL 中,对某一行执行 UPDATE 或 DELETE 时,不会立即移除该行的旧版本。这种方法对于获得多版本并发控制(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,而不是先 DELETE 再 VACUUM。TRUNCATE 会立即移除表的全部内容,而不需要后续再执行 VACUUM 或 VACUUM 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 保留了一个称为 FrozenXID 的特殊 XID。该 XID 不遵循普通 XID 的比较规则,始终被视为比每个普通 XID 都老。普通 XID 使用模 232 算术进行比较。这意味着,对每个普通 XID,都有 20 亿个 XID 比它“更老”,另有 20 亿个 XID 比它“更新”;换句话说,普通 XID 空间是一个没有端点的环。因此,用某个普通 XID 创建行版本后,无论该 XID 是多少,在接下来的 20 亿个事务中,该行版本都会被视为处于“过去”。如果经过超过 20 亿个事务后它仍然存在,就会突然被视为处于未来。为避免这种情况,旧的行版本在到达 20 亿事务这一年限之前,必须被重新赋予 XID FrozenXID。一旦被赋予这个特殊 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 的一个缺点是,它可能导致 VACUUM 做无用功:如果某个行版本随后不久就被修改(因而获得新的 XID),把表行的 XID 改为 FrozenXID 就是浪费时间。另一个缺点是,关于究竟是哪个事务插入或修改了某一行的细节会更早丢失。这些信息有时很有用,特别是在数据库故障后试图分析哪里出了问题时。由于这两个原因,除了完全静态的表之外,不建议减小该设置。

为了跟踪数据库中最老 XID 的年龄,VACUUM 会在系统表 pg_class 和 pg_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个事务、使用了 VACUUM 的 FREEZE 选项,或者所有页面碰巧都需要清理以移除死行版本时,就会扫描整个表。当 VACUUM 扫描整个表时,它应把 age(relfrozenxid) 设为略高于所用 vacuum_freeze_min_age 设置的值(更高的部分等于自 VACUUM 开始以来已启动的事务数)。如果直到达到 autovacuum_freeze_max_age 之前,该表都没有执行一次全表扫描的 VACUUM,那么很快就会被强制执行一次自动清理。

如果由于某种原因,自动清理未能清除表中的旧 XID,当数据库中最老的 XID 距离回卷点只剩 1000 万个事务时,系统就会开始发出类似以下的警告:

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

所谓“自动清理守护进程”实际上由多个进程组成。其中有一个持久运行的守护进程,称为自动清理启动器,负责为所有数据库启动自动清理工作进程。启动器会把工作分散在时间上,力图在每个数据库中每隔autovacuum_naptime秒启动一个工作进程。(因此,如果安装中有 N 个数据库,就会每隔 autovacuum_naptime/N 秒启动一个新的工作进程。)同时最多允许运行autovacuum_max_workers个工作进程。如果需要处理的数据库数量多于 autovacuum_max_workers,那么一旦第一个工作进程结束,就会处理下一个数据库。每个工作进程会检查其数据库中的每个表,并根据需要执行 VACUUM 和/或 ANALYZE。

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

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

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

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

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

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

除了基础阈值和比例因子之外,还有六个可以通过存储参数为每张表设置的自动清理参数。第一个参数 autovacuum_enabled 可以设为 false,以指示自动清理后台进程完全跳过该表。在这种情况下,只有为防止事务 ID 回卷所必需时,自动清理才会处理该表。另外两个参数 autovacuum_vacuum_cost_delay 和 autovacuum_vacuum_cost_limit,用于为基于代价的清理延迟特性设置表级值(见第 18.4.3 节)。autovacuum_freeze_min_age、autovacuum_freeze_max_age 和 autovacuum_freeze_table_age 分别用于设置vacuum_freeze_min_age、autovacuum_freeze_max_age和vacuum_freeze_table_age的值。

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

报告文档问题

阅读 上游文档. 反馈更正前请先核对 当前版本手册.