F.2. amcheck — 验证表和索引一致性的工具#
amcheck
模块提供了允许您验证关系结构的逻辑一致性的函数。
B-Tree 检查函数验证特定关系表示结构中的各种不变量。索引扫描和其他重要操作背后的访问方法函数的正确性依赖于这些不变量始终成立。例如,某些函数验证的内容包括所有 B-Tree 页面的项都按“逻辑”顺序排列(例如,对于text
上的 B-Tree 索引,索引元组应按整理后的词法顺序排列)。如果该特定不变量以某种方式不成立,我们可以预期对受影响页面的二进制搜索错误地指导索引扫描,从而导致对 SQL 查询的错误回答。如果结构看起来有效,则不会引发错误。
验证使用与索引扫描自身相同的过程执行,该过程可能是用户定义的操作符类代码。例如,B-Tree 索引验证依赖于与一个或多个 B-Tree 支持函数 1 例程进行的比较。有关操作符类支持函数的详细信息,请参见第 38.16.3 节。
与通过引发错误来报告损坏的 B 树检查函数不同,堆检查函数verify_heapam
会检查表并尝试返回一组行,每检测到一个损坏就返回一行。尽管如此,如果verify_heapam
所依赖的工具本身已损坏,则该函数可能无法继续运行,而可能引发错误。
可以向非超级用户授予执行amcheck
函数的权限,但在授予此类权限之前,应仔细考虑数据安全和隐私问题。虽然这些函数生成的损坏报告不会过多关注损坏数据的具体内容,而更多关注该数据的结构和发现的损坏的性质,但获得执行这些函数权限的攻击者(尤其是如果攻击者还可以诱发损坏),可能能够从这些消息中推断出一些数据本身。
F.2.1. 函数#
bt_index_check(index regclass, heapallindexed 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
语句在关系上获取的锁定模式相同。当heapallindexed
为true
时,bt_index_check
不会验证跨子/父关系的不变量,但会验证索引中所有堆元组作为索引元组的存在。在实时生产环境中需要例程、轻量级的损坏测试时,使用bt_index_check
通常可以在验证的彻底性与限制对应用程序性能和可用性的影响之间提供最佳平衡。bt_index_parent_check(index regclass, heapallindexed boolean, rootdescend boolean) returns void
bt_index_parent_check
测试其目标(B 树索引)是否符合各种不变量。当heapallindexed
参数为true
时,该函数会验证索引中应找到的所有堆元组的存在。当可选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
)。这些锁可防止并发数据修改,包括INSERT
、UPDATE
和DELETE
命令。这些锁还可防止基础关系被VACUUM
以及所有其他实用程序命令并发处理。请注意,该函数仅在运行时持有锁,而不是在整个事务中。bt_index_parent_check
的附加验证更有可能检测到各种病理情况。这些情况可能涉及由所检查的索引使用的错误实现的 B 树操作符类,或者假设基础 B 树索引访问方法代码中存在未发现的错误。请注意,与bt_index_check
不同,当启用热备用模式(即在只读物理副本上)时,无法使用bt_index_parent_check
。
提示
bt_index_check
和bt_index_parent_check
都在DEBUG1
和DEBUG2
严重级别输出有关验证过程的日志消息。这些消息提供有关验证过程的详细信息,这可能对PostgreSQL开发人员感兴趣。高级用户也可能发现此信息很有用,因为它在验证实际检测到不一致时提供了其他上下文。在运行验证查询之前,在交互式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) 返回记录的 setof
检查表、序列或物化视图的结构损坏(关系中的页面包含格式无效的数据)和逻辑损坏(页面结构有效,但与数据库集群的其余部分不一致)。
识别以下可选参数
on_error_stop
如果为 true,则损坏检查在找到任何损坏的第一个块的末尾停止。
默认为 false。
check_toast
如果为 true,则会根据目标关系的 TOAST 表检查已转换的值。
已知此选项很慢。此外,如果 toast 表或其索引损坏,则根据 toast 值检查它可能会使服务器崩溃,尽管在许多情况下只会产生一个错误。
默认为 false。
skip
如果非
none
,则损坏检查会跳过标记为全部可见或全部冻结的块(如指定的那样)。有效选项为all-visible
、all-frozen
和none
。默认为
none
。startblock
如果指定,则损坏检查从指定的块开始,跳过所有先前的块。在目标表中的块范围之外指定
startblock
是一个错误。默认情况下,检查从第一个块开始。
endblock
如果指定,则损坏检查在指定的块结束,跳过所有剩余的块。在目标表中的块范围之外指定
endblock
是一个错误。默认情况下,检查所有块。
对于检测到的每个损坏,
verify_heapam
返回具有以下列的行blkno
包含损坏页面的块号。
offnum
损坏元组的 OffsetNumber。
attnum
元组中损坏列的属性号(如果损坏特定于某一列,而不是整个元组)。
msg
描述检测到的问题的消息。
F.2.2. 可选*heapallindexed
*验证#
当 B 树验证函数的*heapallindexed
参数为true
时,将针对与目标索引关系关联的表执行额外的验证阶段。这包括一个“虚拟”CREATE INDEX
操作,该操作根据临时内存汇总结构(在验证的基本第一阶段需要时构建)检查所有假设的新索引元组是否存在。汇总结构“指纹”在目标索引中找到的每个元组。heapallindexed
*验证背后的高级原则是,与现有目标索引等效的新索引只能具有可在现有结构中找到的条目。
额外的*heapallindexed
阶段增加了大量开销:验证通常需要花费数倍的时间。但是,在执行heapallindexed
*验证时,对关系级别锁没有更改。
汇总结构的大小受maintenance_work_mem
限制。为了确保每个应该在索引中表示的堆元组的检测不一致性概率不超过 2%,每个元组大约需要 2 字节的内存。每个元组可用的内存越少,则错过不一致性的概率就会缓慢增加。此方法显著限制了验证开销,同时仅略微降低了检测问题(尤其是将验证视为例行维护任务的安装)的概率。任何单个缺失或格式错误的元组都有机会在每次新的验证尝试中被检测到。
F.2.3. 有效使用amcheck
#
amcheck
可以有效检测数据校验和无法捕获的各种类型的故障模式。这些包括
由不正确的操作员类实现导致的结构不一致性。
这包括由操作系统排序规则更改导致的问题。可排序类型的日期(如
text
)的比较必须是不可变的(就像用于 B 树索引扫描的所有比较必须是不可变的一样),这意味着操作系统排序规则永远不能更改。虽然很少见,但操作系统排序规则的更新可能会导致这些问题。更常见的是,主服务器和备用服务器之间的排序顺序不一致,可能是因为正在使用的 主要 操作系统版本不一致。此类不一致性通常只会在备用服务器上出现,因此通常只能在备用服务器上检测到。如果出现此类问题,它可能不会影响使用受影响的排序规则排序的每个索引,仅仅是因为 已编入索引的 值可能碰巧具有相同的绝对排序,而不管行为不一致如何。有关 PostgreSQL 如何使用操作系统区域设置和排序规则的更多详细信息,请参见 第 24.1 节 和 第 24.2 节。
索引与其被索引的堆关系之间的结构不一致性(当执行
heapallindexed
验证时)。在正常操作期间,不会对索引与其堆关系进行交叉检查。堆损坏的症状可能很微妙。
由 PostgreSQL 访问方法代码、排序代码或事务管理代码中假设的未发现错误导致的损坏。
对索引结构完整性的自动验证在对新或建议的 PostgreSQL 功能的一般测试中发挥作用,这些功能可能允许引入逻辑不一致性。表结构和关联的可见性和事务状态信息的验证也发挥着类似的作用。一种明显的测试策略是在运行标准回归测试时连续调用
amcheck
函数。有关运行测试的详细信息,请参见 第 33.1 节。文件系统或存储子系统故障,其中恰好未启用校验和。
请注意,如果在访问块时仅命中共享缓冲区,则
amcheck
会在验证时检查以某种共享内存缓冲区表示的页面。因此,amcheck
并不一定会在验证时检查从文件系统读取的数据。请注意,当启用校验和时,amcheck
可能会在将损坏的块读入缓冲区时因校验和失败而引发错误。由有故障的 RAM 或更广泛的内存子系统引起的损坏。
PostgreSQL 不会防止可纠正的内存错误,并且假定您将使用采用行业标准错误纠正码 (ECC) 或更好保护的 RAM 进行操作。但是,ECC 内存通常仅对单比特错误免疫,不应假定它可以提供 绝对 保护,防止导致内存损坏的故障。
当执行
heapallindexed
验证时,通常会大大增加检测单比特错误的机会,因为测试了严格的二进制相等性,并且测试了堆中的索引属性。
结构损坏可能是由于存储硬件故障或关系文件被不相关的软件覆盖或修改造成的。这种损坏也可以通过数据页校验和检测到。
正确格式化、内部一致且相对于其自身内部校验和正确的关系页面仍可能包含逻辑损坏。因此,这种损坏无法通过校验和检测到。示例包括主表中缺少吐司表中相应条目的吐司值,以及主表中具有比数据库或群集中最旧有效事务 ID 更早的事务 ID 的元组。
在生产系统中观察到逻辑损坏的多种原因,包括PostgreSQL服务器软件中的错误、有故障且构思不周的备份和恢复工具以及用户错误。
损坏的关系在实时生产环境中最为令人担忧,而恰恰在这些环境中,高风险活动最不受欢迎。出于此原因,verify_heapam
已被设计为在不造成不当风险的情况下诊断损坏。它无法防止后端崩溃的所有原因,因为即使执行调用查询在严重损坏的系统上也可能不安全。对目录表的访问已执行,如果目录本身已损坏,则可能存在问题。
一般来说,amcheck
只能证明损坏的存在;它无法证明损坏的缺失。
F.2.4. 修复损坏#
由amcheck
引起的有关损坏的任何错误都不应是误报。amcheck
在按定义永远不应该发生的情况中引发错误,因此通常需要仔细分析amcheck
错误。
没有修复amcheck
检测到的问题的通用方法。应寻求不变性违规的根本原因的解释。pageinspect在诊断amcheck
检测到的损坏方面可能发挥有用的作用。REINDEX
可能无法有效修复损坏。