日常清理

PostgreSQL 9.4.4 中文手册
上一页 | 上一级 | 章 23. 日常数据库维护工作 | 下一页

# 23.1. 日常清理

PostgreSQL数据库需要定期/维护被称为vacuuming。 很多安装足以通过autovacuum守护进程执行清理，正如第 23.1.6 节所描述的。 你可能需要调整清理参数为你的情况得到最好的结果。一些数据库管理员会想 补充或取代手动管理`VACUUM`命令的进程活动， 这通常根据cron或者任务调度程序脚本执行的。 设置手动管理适当清理，理解下面几个子部分讨论的问题是必要的。依赖于autovacuuming的管理员可能仍然希望 浏览此材料来帮助他们理解和调整autovacuuming。

## 23.1.1. 清理基础

PostgreSQL的VACUUM命令 由于以下几个原因，必须周期性处理每个表：

1. 恢复那些由已更新或已删除的行占据的磁盘空间

2. 更新PostgreSQL查询规划器使用的数据统计信息。

3. 更新可见性映射，这加速了index-only扫描

4. 避免因为事务ID重叠或包括multixact 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.4 节 获取更多信息。

## 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而不是后面跟着`VACUUM`‘ 的`DELETE`。 `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 配置参数改变缺省数据库。

> 另外，默认情况下有选择性函数的有限信息可用。但是， 如果您使用函数调用创建一个表达式索引，有用的统计数据将 收集有关函数的信息，这样可以使用表达式索引大大提高查询规划。

> 提示: 该自动清理后台程序不会为外表发出`ANALYZE`命令， 因为它没有办法决定多长时间可能是有用的。如果您的查询需要外表的统计信息进行适当的规划， 在表上合适的时间运行手动管理`ANALYZE`命令是一个好主意。

## 23.1.4. 更新可见视图

清理保持可见视图为了每个表跟踪只包含元组的页面， 对所有活动事务可见（以及所有未来的事务，直至页面再次修改）。这有两个目的。首先，在下次运行时清理 本身可以跳过这些页面，因为没有什么可清理的。

其次，它允许PostgreSQL回答一些只使用索引，没有参考基础表的查询。 由于PostgreSQL索引不包含能见度信息元组 ，普通索引扫描为每个匹配索引项抓取堆元组， 检查它是否由当前事务可见。另外一方面，索引扫描首先检查 能见度视图。如果它知道，页面上的所有元组是 可见的，可用忽略堆抓取。在大型数据集上这是最显著的。 其中可见视图可以防止磁盘访问。 可见视图远远比堆小，所以即使堆非常大，它可以很容易地缓存。

## 23.1.5. 避免事务ID重叠造成的问题

PostgreSQL的MVCC事务语意依赖于比较事务 ID(XID)的数值： 一条带有大于当前事务XID的插入XID的行版本是"属于未来的"， 并且不应为当前事务可见。但是因为事务ID的大小有限(在我们写这些的时候是32位)， 如果集群一次运行的时间很长(大于40亿次事务)，那么它就要受到事务ID重叠的折磨： XID计数器回到零位，然后突然间所有以前的事务就变成看上去是在将来的— 这意味着它们的输出将变得可见。简而言之，可怕的数据丢失。实际上数据仍然在那里， 但是如果你无法获取数据，这么说也只是自我安慰罢了。 为了避免这种情况，有必要清理至少每二十亿事务的每个数据库中的每个表。

周期性的运行VACUUM可以解决这个问题的原因在于`VACUUM` 将标记行为frozen，表明它们是通过一个事务插入的， 该事务的提交足够久远，从MVCC的角度到所有当前和未来事务的角度看， 插入事务的影响确定可以看得到。PostgreSQL 储存特殊的XID（`FrozenTransactionId`）它不遵循普通XID比较规则， 总是被认为比任何普通的XID旧。 普通的XID使用模-232算法进行比较。 这就意味着对于每个普通的XID， 总是有二十亿个XID是"更旧"以及二十亿个XID"更新"； 表达这个意思的另外一个方法是普通的XID 空间是没有终点的环。因此，一旦某行带着特定的普通XID创建出来， 那么该行将在以后的二十亿次事务中表现得是"在过去"，而不管我们说的是哪个普通XID。 如果该行在超过二十亿次事务之后仍然存在，那么它就会突然变成在将来的行。 为了避免数据丢失，将冻结的行版本看做它们的插入XID是`FrozenTransactionId`， 这样它们在所有普通事务面前表现为"在过去"，而不管重叠问题， 并且这样的行版本不管存放多长时间都将是合法的，直到删除。

vacuum_freeze_min_age 控制着在它的行版本将被冻结之前旧的XID值会是多少。 如果将要被冻结的行不久之后会被再次修改，那么增加这个设置可以避免不必要的工作， 但是减小这个设置增加了在表必须被清理之前可以清理事务的数量。

`VACUUM`通常会忽略没有任何死行版本页面， 但这些页面可能仍然有旧XID值的行版本。为了确保所有旧的行版本已经被冻结， 需要全表扫描。vacuum_freeze_table_age控制 `VACUUM`的执行：为了`vacuum_freeze_table_age` 减去`vacuum_freeze_min_age`事务，如果没有完全扫描整个表， 则将其设置为0，强制`VACUUM`总是扫描所有页面，有效地忽略可见视图。

表在清理之前允许执行的最大事务次数 是20亿事务减去`VACUUM`上次扫描整个表时的`vacuum_freeze_min_age`值。 如果超过这个限制就很可能造成数据丢失。为了保证数据安全， 必须在任何可能包含旧于autovacuum_freeze_max_age指定的XID的未冻结行的 表上调用autovacuum。甚至在autovacuum被禁用的情况下也可以调用。

这就意味着，一个未被清理的表将会在大约`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`之后的事务存储提交状态。 每个事务提交状态使用2字节，因此如果`autovacuum_freeze_max_age` 设置为最大允许值为20亿，`pg_clog`将会增加到大约500M。 如果这个尺寸比起你的数据库来只是小菜一碟，我们推荐你将 `autovacuum_freeze_max_age`设为允许的最大值。否则， 如何设置将取决于你愿意给`pg_clog`多大的空间。默认值是2亿， 大约需要50MB的`pg_clog`存储空间。

减小`vacuum_freeze_min_age` 的不利之处是可能导致`VACUUM`做无用功： 如果行在不久之后就被修改，那么冻结一个行版本就是在浪费时间， 因为它很快就将获得一个新的XID。 因此这个设置应当足够大以使得行不被过早的冻结。

为了跟踪数据库中最老的未冻结XID寿命， `VACUUM`在系统表`pg_class`和`pg_database` 里存储了XID统计。 尤其是一个数据库的`pg_class`行中的`relfrozenxid`字段 包含了最后一个整表`VACUUM`命令使用的冻结终止XID。 系统保证在所有比这个终止XID老的事务插入的行都被冻结。 同样，一个数据库的`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 IN ('r', 'm');

    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`启动之后开始的事务数目稍大)。如果在表上提出非全表扫描`VACUUM`直到 超过`autovacuum_freeze_max_age`，则将会很快在表上强制进行自动清理。

如果从表中清理旧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 vacuum that database in single-user mode.

这个100万的事务安全边界留下来用于让管理员在不丢失数据的情况下进行恢复， 方法是手工执行所需要的`VACUUM`命令。不过，因为一旦进入了安全关闭模式， 系统就不能再执行命令，做这件事情的唯一的方法是停止主服务器， 使用单用户模式来执行`VACUUM`。关闭模式不会强制于单用户模式。 参阅postgres手册获取有关使用单用户模式的细节。

### 23.1.5.1. Multixacts 和 Wraparound

Multixact IDs用于支持多个事务的行锁。 因为元组头部只有有限的空间存储锁信息，为了简短， 该信息编码为一个"多事务ID"，或multixact ID， 每当这里有多个事务并发的锁定一行时。关于哪个事务ID包含在任何一个特定的 multixact ID中的信息是单独存储在`pg_multixact`子目录中的， 并且只有出现在`xmax`字段中的multixact ID在元组头中。 类似事务ID，multixact ID是作为32位计数器和相应的存储实现的， 所有这些都需要小心的老化管理、存储清理和重叠处理。 有一个单独的存储区域保存每个multixact的成员列表， 它也使用32位的计数器，并且该计数器也需要管理。

在`VACUUM`表扫描期间，部分或全表扫描，任何比 vacuum_multixact_freeze_min_age 老的multixact ID被一个不同的值替换，这个值可以是0、单事务ID或一个较新的multixact ID。 对于每个表，`pg_class`.`relminmxid` 存储仍然出现在该表的任何元组中的最老的multixact ID。 如果该值比vacuum_multixact_freeze_table_age老， 那么强制全表扫描。全表`VACUUM`扫描，忽略原因，推进该表的值。 最终，扫描了所有数据库中的所有表，并且它们的最老的multixact值都推进了， 磁盘上最老的multixact可以删除了。

作为一个安全策略，multixact-age大于 autovacuum_multixact_freeze_max_age 的表将发生全表清理扫描。全表清理扫描也将逐渐发生在所有的表中， 从拥有最老multixact-age的表开始，如果已经使用的成员存储空间超过了地址存储空间的50%。 这些种类的全表扫描都将发生，虽然自动清理通常是禁用的。

## 23.1.6. Autovacuum守护进程

PostgreSQL带有一个可选高度推荐的特性 叫做autovacuum守护进程， 它的目的是自动执行`VACUUM`和`ANALYZE` 命令。 在打开这个选项之后，autovacuum守护进程将检查那些有大量插入、 更新、删除行操作的表。这些检查使用行级别的统计收集设施；因此，除非把 track_counts设置为`true`， 否则无法使用autovacuum守护进程。 在缺省配置下，启用autovacuum守护进程并且合理设置相关配置参数。

该"自动清理后台程序"实际上是由多个进程组成的。 有一个持久守护进程，称为autovacuum launcher 它是负责为所有数据库启动autovacuum worker进行。 该发射器将分发工作跨越时间， 每个数据库内每autovacuum_naptime秒内尝试启动1个工作。 （因此，如果安装有`N`个数据库，每`autovacuum_naptime`/`N`秒将开始一个新的。） 最多autovacuum_max_workers工作进程在同一时间允许运行。 如果正在处理多于`autovacuum_max_workers`的数据库，一旦第一个处理完成将处理 下一个数据库。每个工作进程将检查它的数据库中的每个表，并且 执行`VACUUM`和/或者按需要执行`ANALYZE`。 使用`log_autovacuum_min_duration`可以监控 自动清理活动。

如果在很短的时间中需要清理若干个大表，则 所有自动清理的工人可能需要很长一段时间清理这些表。 这将导致其它表和数据库不能被清理，直到工人可用。 在单一的数据库中有多少人可能没有限制， 但尽量避免已经被其他人完成的重复工作。需要注意的是运行数 不计入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 节获取有关全局设置的更多细节。

除了基本阈值和缩放系数之外， 还有6个autovacuum 参数可以通过存储参数为每个表进行设置。 第一个参数，`autovacuum_enabled`可以设置为`false`让autovacuum 守护进程完全忽略某个表。这种情况下，autovacuum只有在为了避免事务ID 重叠必须清理整个数据库的时候才会动那个表。接下来两个参数， `autovacuum_vacuum_cost_delay`和`autovacuum_vacuum_cost_limit` 用于针对特定的表为基于开销的清理延迟特性设置数值。参阅第 18.4.4 节。 `autovacuum_freeze_min_age`, `autovacuum_freeze_max_age`和 `autovacuum_freeze_table_age`分别为vacuum_freeze_min_age, autovacuum_freeze_max_age和 vacuum_freeze_table_age设置数值。

当多个工作者正在运行，成本延迟参数在所有正在运行的人中是"balanced"， 从而使系统上的总I/O影响是相同的，而不管实际运行工作数。 不过，任何已经设置了`autovacuum_vacuum_cost_delay`或 `autovacuum_vacuum_cost_limit`工作进程表都不考虑在平衡算法中。

上一页 | 起始页 | 下一页
日常数据库维护工作 | 上一级 | 经常重建索引