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