2024年9月26日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:开发版
不受支持的版本:11 / 10

F.1. amcheck — 用于验证表和索引一致性的工具 #

amcheck 模块提供了一些函数,允许您验证关系结构的逻辑一致性。

B 树检查函数验证特定关系表示结构中的各种不变式。索引扫描和其他重要操作背后的访问方法函数的正确性依赖于这些不变式始终保持有效。例如,某些函数除了其他事项外,还验证所有 B 树页面上的项是否按逻辑顺序排列(例如,对于 text 上的 B 树索引,索引元组应按排序的词法顺序排列)。如果该特定不变式以某种方式失效,我们可以预期受影响页面的二分搜索将错误地引导索引扫描,从而导致 SQL 查询的错误答案。如果结构看起来有效,则不会引发错误。在运行这些检查函数时,search_path 会临时更改为 pg_catalog, pg_temp

验证使用与索引扫描本身使用的相同过程执行,这些过程可能是用户定义的操作符类代码。例如,B 树索引验证依赖于使用一个或多个 B 树支持函数 1 例程进行的比较。有关操作符类支持函数的详细信息,请参见第 36.16.3 节

与报告损坏(通过引发错误)的 B 树检查函数不同,堆检查函数 verify_heapam 检查表并尝试返回一组行,每个检测到的损坏对应一行。尽管如此,如果 verify_heapam 依赖的设施本身已损坏,则该函数可能无法继续执行,而是可能引发错误。

可以授予非超级用户执行 amcheck 函数的权限,但在授予此类权限之前,应仔细考虑数据安全和隐私问题。虽然这些函数生成的损坏报告并不那么关注损坏数据的內容,而是关注数据的结构以及发现的损坏的性质,但获得执行这些函数权限的攻击者,尤其是在攻击者还可以诱发损坏的情况下,可能会根据此类消息推断出一些数据本身的信息。

F.1.1. 函数 #

bt_index_check(index regclass, heapallindexed boolean, checkunique boolean) returns void

bt_index_check 测试其目标(一个 B 树索引)是否符合各种不变式。示例用法

test=# SELECT bt_index_check(index => c.oid, heapallindexed => i.indisunique),
               c.relname,
               c.relpages
FROM pg_index i
JOIN pg_opclass op ON i.indclass[0] = op.oid
JOIN pg_am am ON op.opcmethod = am.oid
JOIN pg_class c ON i.indexrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE am.amname = 'btree' AND n.nspname = 'pg_catalog'
-- Don't check temp tables, which may be from another session:
AND c.relpersistence != 't'
-- Function may throw an error when this is omitted:
AND c.relkind = 'i' AND i.indisready AND i.indisvalid
ORDER BY c.relpages DESC LIMIT 10;
 bt_index_check |             relname             | relpages
----------------+---------------------------------+----------
                | pg_depend_reference_index       |       43
                | pg_depend_depender_index        |       40
                | pg_proc_proname_args_nsp_index  |       31
                | pg_description_o_c_o_index      |       21
                | pg_attribute_relid_attnam_index |       14
                | pg_proc_oid_index               |       10
                | pg_attribute_relid_attnum_index |        9
                | pg_amproc_fam_proc_index        |        5
                | pg_amop_opr_fam_index           |        5
                | pg_amop_fam_strat_index         |        5
(10 rows)

此示例显示了一个会话,该会话执行对数据库test中 10 个最大目录索引的验证。对于作为唯一索引的子集,请求验证堆元组作为索引元组的存在。由于没有引发错误,因此所有测试的索引似乎在逻辑上都是一致的。当然,此查询可以很容易地更改为对数据库中支持验证的每个索引调用 bt_index_check

bt_index_check 对目标索引及其所属的堆关系获取 AccessShareLock。此锁定模式与简单的 SELECT 语句对关系获取的锁定模式相同。bt_index_check 不会验证跨越子/父关系的不变式,但会在 heapallindexedtrue 时验证所有堆元组作为索引元组的存在。当 checkuniquetrue 时,bt_index_check 将检查唯一索引中重复条目中最多只有一个可见。当在实时生产环境中需要进行例行、轻量级的损坏测试时,使用 bt_index_check 通常可以在验证的彻底性和限制对应用程序性能和可用性的影响之间提供最佳的权衡。

bt_index_parent_check(index regclass, heapallindexed boolean, rootdescend boolean, checkunique boolean) returns void

bt_index_parent_check 测试其目标(一个 B 树索引)是否符合各种不变式。可选地,当 heapallindexed 参数为 true 时,该函数验证所有应在索引中找到的堆元组的存在。当 checkuniquetrue 时,bt_index_parent_check 将检查唯一索引中重复条目中最多只有一个可见。当可选的 rootdescend 参数为 true 时,验证通过对每个元组从根页面执行新的搜索来重新查找叶级上的元组。bt_index_parent_check 可以执行的检查是 bt_index_check 可以执行的检查的超集。bt_index_parent_check 可以被认为是 bt_index_check 的更彻底的变体:与 bt_index_check 不同,bt_index_parent_check 还检查跨越父/子关系的不变式,包括检查索引结构中是否存在丢失的下行链接。bt_index_parent_check 遵循一般的约定,即如果发现逻辑不一致或其他问题,则引发错误。

bt_index_parent_check 需要对目标索引进行 ShareLock(还会对堆关系获取 ShareLock)。这些锁可以防止并发数据修改来自 INSERTUPDATEDELETE 命令。这些锁还阻止底层关系被 VACUUM 以及所有其他实用程序命令并发处理。请注意,该函数仅在运行时持有锁,而不是在整个事务期间持有锁。

bt_index_parent_check 的附加验证更有可能检测到各种病态情况。这些情况可能涉及由被检查的索引使用的错误实现的 B 树操作符类,或者假设情况下,底层 B 树索引访问方法代码中未发现的错误。请注意,与 bt_index_check 不同,当启用热备用模式时(即在只读物理副本上),bt_index_parent_check 无法使用。

提示

bt_index_checkbt_index_parent_check 都在 DEBUG1DEBUG2 严重性级别输出有关验证过程的日志消息。这些消息提供有关验证过程的详细信息,这些信息可能对 PostgreSQL 开发人员感兴趣。高级用户也可能会发现这些信息很有用,因为如果验证确实检测到不一致,它会提供其他上下文。运行

SET client_min_messages = DEBUG1;

在交互式 psql 会话中,然后再运行验证查询,将以合理的详细程度显示有关验证进度的消息。

verify_heapam(relation regclass, on_error_stop boolean, check_toast boolean, skip text, startblock bigint, endblock bigint, blkno OUT bigint, offnum OUT integer, attnum OUT integer, msg OUT text) returns setof record

检查表、序列或物化视图是否存在结构损坏(其中关系中的页面包含格式错误的数据)以及是否存在逻辑损坏(其中页面结构有效,但与数据库集群的其余部分不一致)。

识别以下可选参数

on_error_stop

如果为真,则损坏检查在发现任何损坏的第一个块的末尾停止。

默认为假。

check_toast

如果为真,则将烤面包值与目标关系的 TOAST 表进行检查。

此选项已知速度较慢。此外,如果 toast 表或其索引已损坏,则将其与 toast 值进行检查可能会导致服务器崩溃,尽管在许多情况下这只会产生错误。

默认为假。

skip

如果不是 none,则损坏检查将跳过标记为全部可见或全部冻结的块,如指定。有效选项为 all-visibleall-frozennone

默认为 none

startblock

如果指定,则损坏检查从指定的块开始,跳过所有之前的块。指定目标表块范围之外的 startblock 是错误的。

默认情况下,检查从第一个块开始。

endblock

如果指定,则损坏检查在指定的块结束,跳过所有剩余的块。指定目标表块范围之外的 endblock 是错误的。

默认情况下,检查所有块。

对于检测到的每个损坏,verify_heapam 返回一行,其中包含以下列

blkno

包含损坏页面的块号。

offnum

损坏元组的 OffsetNumber。

attnum

元组中损坏列的属性号,如果损坏特定于列而不是整个元组。

msg

描述检测到的问题的消息。

F.1.2. 可选的 heapallindexed 验证 #

当传递给 B 树验证函数的 heapallindexed 参数为 true 时,会针对与目标索引关系关联的表执行一个额外的验证阶段。这包括一个 虚拟 CREATE INDEX 操作,该操作会检查所有假设的新索引元组是否存在于一个临时的内存中汇总结构中(此结构在验证的基本第一阶段需要时构建)。汇总结构会为目标索引中找到的每个元组 指纹heapallindexed 验证背后的高级原理是,等效于现有目标索引的新索引只能包含在现有结构中找到的条目。

额外的 heapallindexed 阶段会增加大量的开销:验证通常会花费数倍的时间。但是,当执行 heapallindexed 验证时,关系级锁的获取不会发生变化。

汇总结构的大小受 maintenance_work_mem 限制。为了确保对于每个应该在索引中表示的堆元组,检测到不一致的概率不超过 2%,每个元组大约需要 2 字节的内存。随着为每个元组提供的内存减少,遗漏不一致的概率会缓慢增加。这种方法显着限制了验证的开销,同时仅略微降低了检测问题的概率,尤其是在将验证视为例行维护任务的安装中。任何单个缺失或格式错误的元组在每次新的验证尝试中都有新的机会被检测到。

F.1.3. 有效使用 amcheck #

amcheck 可以有效地检测到 数据校验和 无法捕获的各种类型的故障模式。这些包括

  • 由不正确的操作符类实现导致的结构性不一致。

    这包括由操作系统排序规则的比较规则更改引起的问题。可排序类型的(如 text)数据的比较必须是不可变的(就像用于 B 树索引扫描的所有比较必须是不可变的一样),这意味着操作系统排序规则绝不能更改。尽管很少见,但操作系统排序规则的更新可能会导致这些问题。更常见的是,主服务器和备用服务器之间的排序顺序不一致,可能是因为使用的 主要 操作系统版本不一致。此类不一致通常只会出现在备用服务器上,因此通常只能在备用服务器上检测到。

    如果出现此类问题,它可能不会影响每个使用受影响的排序规则排序的单个索引,仅仅因为 索引 值碰巧具有相同的绝对顺序,而不管行为不一致如何。有关 PostgreSQL 如何使用操作系统区域设置和排序规则的更多详细信息,请参阅 第 23.1 节第 23.2 节

  • 索引与其被索引的堆关系之间的结构性不一致(当执行 heapallindexed 验证时)。

    在正常操作期间,不会交叉检查索引与其堆关系。堆损坏的症状可能很微妙。

  • 由底层 PostgreSQL 访问方法代码、排序代码或事务管理代码中假设的未发现的 bug 引起的损坏。

    索引结构完整性的自动验证在对可能允许引入逻辑不一致的新或建议的 PostgreSQL 功能进行一般测试中发挥着作用。表结构以及相关的可见性和事务状态信息的验证也发挥着类似的作用。一个明显的测试策略是在运行标准回归测试时连续调用 amcheck 函数。有关运行测试的详细信息,请参阅 第 31.1 节

  • 文件系统或存储子系统故障,其中校验和碰巧只是未启用。

    请注意,如果在访问块时仅发生共享缓冲区命中,则 amcheck 会检查验证时某个共享内存缓冲区中表示的页面。因此,amcheck 不一定会检查验证时从文件系统读取的数据。请注意,当启用校验和时,amcheck 可能会由于读取到缓冲区的块损坏而导致校验和失败而引发错误。

  • 由有故障的 RAM 或更广泛的内存子系统引起的损坏。

    PostgreSQL 不保护免受可纠正的内存错误的影响,并且假设您将使用使用行业标准错误校正代码 (ECC) 或更好的保护的 RAM 进行操作。但是,ECC 内存通常仅对单比特错误免疫,不应假定它提供对导致内存损坏的故障的 绝对 保护。

    当执行 heapallindexed 验证时,检测单比特错误的机会通常会大大增加,因为会测试严格的二进制相等性,并且会测试堆中的索引属性。

结构损坏可能是由于存储硬件故障或关系文件被无关软件覆盖或修改而导致的。这种损坏也可以使用 数据页校验和 检测到。

正确格式化、内部一致且相对于其自身内部校验和正确的关系页可能仍然包含逻辑损坏。因此,这种类型的损坏无法使用 校验和 检测到。示例包括主表中的已烤面包值,这些值在烤面包表中缺少相应的条目,以及主表中元组的事务 ID 早于数据库或集群中最旧的有效事务 ID。

在生产系统中观察到逻辑损坏的多种原因,包括 PostgreSQL 服务器软件中的错误、有故障且设计不良的备份和恢复工具以及用户错误。

损坏的关系在实时生产环境中最令人担忧,而这些环境正是最不欢迎高风险活动的环境。出于这个原因,verify_heapam 被设计为在没有不必要风险的情况下诊断损坏。它无法防御所有导致后端崩溃的原因,因为即使执行调用查询在严重损坏的系统上也可能不安全。访问 目录表 会执行,如果目录本身已损坏,则可能会出现问题。

总的来说,amcheck 只能证明损坏的存在;它不能证明损坏不存在。

F.1.4. 修复损坏 #

amcheck 引发的任何关于损坏的错误都不应该出现误报。 amcheck 在定义上永远不应该发生的条件下引发错误,因此通常需要仔细分析 amcheck 错误。

没有修复 amcheck 检测到的问题的一般方法。应寻找不变量违规根本原因的解释。 pageinspect 可以在诊断 amcheck 检测到的损坏方面发挥作用。 REINDEX 可能无法有效修复损坏。

提交更正

如果您在文档中看到任何不正确的内容,与您对特定功能的体验不符,或者需要进一步澄清,请使用 此表单 报告文档问题。