日常数据库维护工作
- 日常数据库维护工作
- 前言
- 日常清理
- 清理的基础知识
- VACUUM和VACUUM FULL
- 恢复磁盘空间
- 更新规划器统计信息
- 更新可见性映射
- 防止事务 ID 回卷失败
- 自动清理后台进程
- 日常重建索引
- 日志文件维护
前言
和任何数据库软件一样,PostgreSQL需要定期执行特定的任务来达到最优的性能。这里讨论的任务是必需的,但它们本质上是重复性的并且可以很容易使用cron脚本或Windows的任务计划程序等标准工具来自动进行。建立合适的脚本并检查它们是否成功运行是数据库管理员的职责。
- 一个显而易见的维护任务是定期创建数据的后备拷贝。如果没有一个最近的备份,你就不可能在灾难(磁盘失败、或在、错误地删除一个关键表等)后进行恢复。
- 另一种主要类型的维护任务是周期性地“清理”数据库。
- 另一项需要周期性考虑的任务是日志文件管理。
- check_postgres1可用于检测数据库的健康并报告异常情况。check_postgres与Nagios和MRTG整合在一起,但也可以被单独运行。
相对于其他数据库管理系统,PostgreSQL的维护量较低。但是,适当对这些任务加以注意将大有助于愉快和高效地使用该系统。
日常清理
PostgreSQL数据库要求周期性的清理维护。对于很多安装,让自动清理守护进程来执行清理已经足够,如第 25.1.6 节所述。你可能需要调整其中描述的自动清理参数来获得最佳结果。某些数据库管理员会希望使用手动管理的VACUUM命令来对后台进程的活动进行补充或者替换,这通常使用cron或任务计划程序脚本来执行。要正确地设置手动管理的清理,最重要的是理解接下来几小节中讨论的问题。依赖自动清理的管理员最好也能略读该内容以帮助他们理解和调整自动清理。
清理的基础知识
PostgreSQL的 VACUUM命令出于几个原因必须定期处理每一个表:
-
- 恢复或重用被已更新或已删除行所占用的磁盘空间。
-
- 更新被PostgreSQL查询规划器使用的数据统计信息。
-
- 更新可见性映射,它可以加速只用索引的扫描。
-
- 保护老旧数据不会由于事务ID回卷或多事务ID回卷而丢失。
正如后续小节中解释的,每一个原因都将指示以不同的频率和范围执行VACUUM操作。
VACUUM和VACUUM FULL
有两种VACUUM的变体:标准VACUUM和VACUUM FULL。
- VACUUM FULL可以收回更多磁盘空间但是运行起来更慢
- 标准形式的VACUUM可以和生产数据库操作并行运行(SELECT、INSERT、UPDATE和DELETE等命令将继续正常工作,但在清理期间你无法使用ALTER TABLE等命令来更新表的定义)。
- VACUUM FULL要求在其工作的表上得到一个ACCESS EXCLUSIVE 锁,因此无法和对此表的其他使用并行。
- 因此,通常管理员应该努力使用标准VACUUM并且避免VACUUM FULL。
- VACUUM会产生大量I/O流量,这将导致其他活动会话性能变差。
- 可以调整一些配置参数来后台清理活动造成的性能冲击
恢复磁盘空间
在PostgreSQL中,一次行的UPDATE或DELETE不会立即移除该行的旧版本。这种方法对于从多版本并发控制(MVCC,见第 13 章)获益是必需的:当旧版本仍可能对其他事务可见时,它不能被删除。但是最后,任何事务都不会再对一个过时的或者被删除的行版本感兴趣。它所占用的空间必须被回收来用于新行,这样可避免磁盘空间需求的无限制增长。这通过运行VACUUM完成。
VACUUM的标准形式移除表和索引中的死亡行版本并将该空间标记为可在未来重用。不过,它将不会把该空间交还给操作系统,除非在特殊的情况中表尾部的一个或多个页面变成完全空闲并且能够很容易地得到一个排他表锁。相反,VACUUM FULL通过把死亡空间之外的内容写成一个完整的新版本表文件来主动紧缩表。这将最小化表的尺寸,但是要花较长的时间。它也需要额外的磁盘空间用于表的新副本,直到操作完成。
例行清理的一般目标是多做标准的VACUUM来避免需要VACUUM FULL。自动清理守护进程尝试这样工作,并且实际上永远不会发出VACUUM FULL。在这种方法中,其思想不是让表保持它们的最小尺寸,而是保持磁盘空间使用的稳定状态:每个表占用的空间等于其最小尺寸外加清理之间将使用的空间量。尽管VACUUM FULL可被用来把一个表收缩回它的最小尺寸并将该磁盘空间交还给操作系统,但是如果该表将在未来再次增长这样就没什么意义。因此,对于维护频繁被更新的表,适度运行标准VACUUM运行比少量运行VACUUM FULL要更好。
一些管理员更喜欢自己计划清理,例如在晚上负载低时做所有的工作。根据一个固定日程来做清理的难点在于,如果一个表有一次预期之外的更新活动尖峰,它可能膨胀得真正需要VACUUM FULL来回收空间。使用自动清理守护进程可以减轻这个问题,因为守护进程会根据更新活动动态规划清理操作。除非你的负载是完全可以预估的,完全禁用守护进程是不理智的。一种可能的折中方案是设置守护进程的参数,这样它将只对异常的大量更新活动做出反应,因而保证事情不会失控,而在负载正常时采用有计划的VACUUM来做批量工作。
对于那些不使用自动清理的用户,一种典型的方法是计划一个数据库范围的VACUUM,该操作每天在低使用量时段执行一次,并根据需要辅以在重度更新表上的更频繁的清理(一些有着极高更新率的安装会每几分钟清理一次它们的最繁忙的表)。如果你在一个集簇中有多个数据库,别忘记VACUUM每一个,你会用得上vacuumdb程序。
提示
当一个表因为大量更新或删除活动而包含大量死亡行版本时,纯粹的VACUUM可能不能令人满意。如果你有这样一个表并且你需要回收它占用的过量磁盘空间,你将需要使用VACUUM FULL,或者CLUSTER,或者ALTER TABLE的表重写变体之一。这些命令重写该表的一整个新拷贝并且为它构建新索引。所有这些选项都要求ACCESS EXCLUSIVE 锁。
注意:它们也临时使用大约等于该表尺寸的额外磁盘空间,因为直到新表和索引完成之前旧表和索引都不能被释放。
提示
如果你有一个表,它的整个内容会被周期性删除,考虑用TRUNCATE而不是先用DELETE再用VACUUM。TRUNCATE会立刻移除该表的整个内容,而不需要一次后续的VACUUM或VACUUM FULL来回收现在未被使用的磁盘空间。其缺点是会违背严格的MVCC 语义。
更新规划器统计信息
PostgreSQL查询规划器依赖于有关表内容的统计信息来为查询产生好的计划。这些统计信息由ANALYZE命令收集,它除了直接被调用之外还可以作为VACUUM的一个可选步骤被调用。拥有适度准确的统计信息很重要,否则差的计划可能降低数据库性能。
自动清理守护进程如果被启用,当一个表的内容被改变得足够多时,它将自动发出ANALYZE命令。不过,管理员可能更喜欢依靠手动的ANALYZE操作,特别是如果知道一个表上的更新活动将不会影响“感兴趣的”列的统计信息时。守护进程严格地按照一个被插入或更新行数的函数来计划ANALYZE,它不知道那是否将导致有意义的统计信息改变。
正如用于空间恢复的清理一样,频繁更新统计信息对重度更新的表更加有用。但即使对于一个重度更新的表,如果该数据的统计分布没有很大改变,也没有必要更新统计信息。一个简单的经验法则是考虑表中列的最大和最小值改变了多少。例如,一个包含行被更新时间的timestamp列将在行被增加和更新时有一直增加的最大值;这样一列将可能需要更频繁的统计更新,而一个包含一个网站上被访问页面 URL 的列则不需要。URL 列可以经常被更改,但是其值的统计分布的变化相对很慢。
可以在指定表上运行ANALYZE甚至在表的指定列上运行,因此如果你的应用需要,可以更加频繁地更新某些统计。但实际上,通常只分析整个数据库是最好的,因为它是一种很快的操作。ANALYZE对一个表的行使用一种统计的随机采样,而不是读取每一个单一行。
提示
尽管对每列的ANALYZE频度调整可能不是非常富有成效,你可能会发现值得为每列调整被ANALYZE收集统计信息的详细程度。经常在WHERE中被用到的列以及数据分布非常不规则的列可能需要比其他列更细粒度的数据直方图。见ALTER TABLE SETSTATISTICS,或者使用default_statistics_target配置参数改变数据库范围的默认值。
还有,默认情况下关于函数的选择度的可用信息是有限的。但是,如果你创建一个统计对象或者使用函数的表达式索引,关于该函数的有用的统计信息将被收集,这些信息能够大大提高使用该表达式索引的查询计划的质量。
提示
自动清理守护进程不会为外部表发出ANALYZE命令,因为无法确定一个合适的频度。如果你的查询需要外部表的统计信息来正确地进行规划,比较好的方式是按照一个合适的时间表在那些表上手工运行ANALYZE命令。
更新可见性映射
清理机制为每一个表维护着一个可见性映射,它被用来跟踪哪些页面只包含对所有活动事务(以及所有未来的事务,直到该页面被再次修改)可见的元组。这样做有两个目的。
- 第一,清理本身可以在下一次运行时跳过这样的页面,因为其中没有什么需要被清除。
- 第二,这允许PostgreSQL回答一些只用索引的查询,而不需要引用底层表。
因为PostgreSQL的索引不包含元组的可见性信息,一次普通的索引扫描会为每一个匹配的索引项获取堆元组,用来检查它是否能被当前事务所见。另一方面,一次只用索引的扫描会首先检查可见性映射。如果它了解到在该页面上的所有元组都是可见的,堆获取就可以被跳过。这对大数据集很有用,因为可见性映射可以防止磁盘访问。可见性映射比堆小很多,因此即使堆非常大,可见性映射也可以很容易地被缓存起来。
防止事务 ID 回卷失败
PostgreSQL的 MVCC 事务语义依赖于能够比较事务 ID(XID)数字:如果一个行版本的插入 XID 大于当前事务的 XID,它就是“属于未来的”并且不应该对当前事务可见。但是因为事务 ID 的尺寸有限(32位),一个长时间(超过 40 亿个事务)运行的集簇会遭受到事务 ID 回卷问题:XID 计数器回卷到 0,并且本来属于过去的事务突然间就变成了属于未来 — 这意味着它们的输出变成不可见。简而言之,灾难性的数据丢失(实际上数据仍然在那里,但是如果你不能得到它也无济于事)。为了避免发生这种情况,有必要至少每 20 亿个事务就清理每个数据库中的每个表。
周期性的清理能够解决该问题的原因是,VACUUM会把行标记为 冻结,这表示它们是被一个在足够远的过去提交的事务所插入, 这样从 MVCC 的角度来看,效果就是该插入事务对所有当前和未来事务来说当然都 是可见的。PostgreSQL保留了一个特殊的 XID(FrozenTransactionId),这个 XID 并不遵循普通 XID 的比较规则 并且总是被认为比任何普通 XID 要老。普通 XID 使用模-232算 法来比较。这意味着对于每一个普通 XID都有 20亿个 XID “更老”并且 有 20 亿个“更新”,另一种解释的方法是普通 XID 空间是没有端点的环。 因此,一旦一个行版本创建时被分配了一个特定的普通 XID,该行版本将成为接下 来 20 亿个事务的“过去”(与我们谈论的具体哪个普通 XID 无关)。如 果在 20 亿个事务之后该行版本仍然存在,它将突然变得好像在未来。要阻止这一切 发生,被冻结行版本会被看成其插入 XID 为FrozenTransactionId, 这样它们对所有普通事务来说都是“在过去”,而不管回卷问题。并且这样 的行版本将一直有效直到被删除,不管它有多旧。
注意
在9.4之前的PostgreSQL版本中,实际上会通过将一行的插入 XID 替换为FrozenTransactionId来实现冻结,这种FrozenTransactionId在行的 xmin系统列中是可见的。较新的版本只是设置一个标志位, 保留行的原始xmin用于可能发生的鉴别用途。不过, 在9.4之前版本的数据库pg_upgrade中可能仍会找到 xmin等于FrozenTransactionId (2)的行。此外,系统目录可能会包含xmin等于BootstrapTransactionId (1) 的行,这表示它们是在initdb的第一个阶段被插入的。 和FrozenTransactionId相似,这个特殊的 XID被认为比所有正常 XID 的年龄都要老。
自动清理后台进程
日常重建索引
在某些情况下值得周期性地使用REINDEX命令或一系列独立重构步骤来重建索引。
已经完全变成空的B树索引页面被收回重用。但是,还是有一种低效的空间利用的可能性:如果一个页面上除少量索引键之外的全部键被删除,该页面仍然被分配。因此,在这种每个范围中大部分但不是全部键最终被删除的使用模式中,可以看到空间的使用是很差的。对于这样的使用模式,推荐使用定期重索引。
对于非B树索引可能的膨胀还没有很好地定量分析。在使用非B树索引时定期监控索引的物理尺寸是个好主意。
还有,对于B树索引,一个新建立的索引比更新了多次的索引访问起来要略快, 因为在新建立的索引上,逻辑上相邻的页面通常物理上也相邻(这样的考虑目前并不适用于非B树索引)。仅仅为了提高访问速度也值得定期重索引。
REINDEX在所有情况下都可以安全和容易地使用。 默认情况下,此命令需要一个ACCESS
EXCLUSIVE锁,因此通常最好使用CONCURRENTLY选项执行它,该选项仅需要获取SHARE UPDATE EXCLUSIVE锁。
