F.2. amcheck

F.2.1. 函数
F.2.2. 可选的heapallindexed验证
F.2.3. 有效地使用amcheck
F.2.4. 修复损坏

amcheck 模块提供的函数让用户能验证关系结构的逻辑一致性。

这些B-Tree检查函数验证特定关系的结构表达中的各种不变条件。 索引扫描以及其他重要操作背后的访问方法的正确性都要依仗这些不变条件的成立。 例如,在这些函数中,有一些负责验证所有B树页面中的项都按照逻辑顺序(比如,对于text上的B树索引,索引元组应该按照词典顺序排列)摆放。 如果特定的不变条件由于某种原因无法成立,则我们可以预料受影响页面上的二分搜索将无法正确地引导索引扫描,最终导致SQL查询得到错误的答案。 如果结构看起来有效,则不会引发错误。

验证过程采用索引扫描自身使用的同种过程来执行,这些过程可能是用户定义的操作符类代码。例如,B树索引验证依赖于由一个或者多个B树支持函数1例程构成的比较。操作符类支持函数的详情请见第 38.16.3 节

不像通过提示错误来报告损坏的B-Tree检查函数,堆检查函数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语句在关系上所要求的锁模式相同。bt_index_check不验证跨越父子关系的不变条件,但是在heapallindexedtrue时将验证所有堆元组是否作为索引中的索引元组存在。当在生产环境中要求一个使用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)。这些锁阻止来自INSERTUPDATE以及DELETE命令的并发数据修改。这些锁同时防止底层关系被并发的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

如果为真(true),则损坏检查将在被发现有任何损坏的第一个块的末尾停止.

默认为假(false).

check_toast

如果为真(true),toasted值根据目标关系的TOAST表进行检查。

这个选项已知是缓慢的。而且,如果toast表或它的索引损坏了,根据toast值检查它可能会使服务器宕机,虽然在一些情况下仅是产生一个错误。

默认为假(false).

skip

如果不是 none,损坏检查会根据规定忽略被标示为全部可见或全部冻结的块。 有效的选项为all-visible,all-frozennone

默认为 none.

startblock

如果已指定,则损坏检查从指定的块开始,忽略前面所有的块。指定startblock超出目标表的块的范围是一个错误。

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

endblock

如果已经指定,损坏检查在指定的块结束,忽略所有剩余的块。指定endblock超出目标表的块的范围是一个错误。

默认情况下,所有的块都被检测。

对所有检测到的损坏情况, verify_heapam 返回一行包含下述列:

blkno

包含损坏页的块的编号。

offnum

损坏元组的 OffsetNumber。

attnum

如果损坏是指定到列而不是整个元组,则为元组中损坏列的属性号。

msg

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

F.2.2. 可选的heapallindexed验证

当B-Tree验证函数的heapallindexed参数为true时,会针对与目标索引关系关联的表执行一个额外的验证过程。这种验证由一个假的CREATE INDEX操作组成,它针对一个临时的、内存中的汇总结构(根据需要在基础的第一阶段验证过程中建立)检查所有假想的新索引元组的存在。这个汇总结构对目标索引中的每一个元组采集指纹heapallindexed验证背后的高层原则是:等效于现有目标索引的新索引必须仅拥有能在现有结构中找得到的项。

额外的heapallindexed阶段会增加明显的开销:验证的时间通常将会延长几倍。不过,在执行heapallindexed验证时,所要求的关系级锁没有变化。

这一汇总结构的尺寸以maintenance_work_mem为界。为了确保对于每个堆元组应该存在于索引中这一检测有不超过2%的失效概率能检测到不一致,每个元组需要大约2个字节的内存。因为每个元组可用的内存变少,错失一处不一致的概率就会慢慢增加。这种方法显著地限制了验证的开销,但仅仅略微降低了检测到问题的概率,对于将验证当作例行维护任务的安装来说更是如此。对于每一次新的验证尝试,任何单一的缺失或者畸形元组都有新的机会被检测到。

F.2.3. 有效地使用amcheck

amcheck对于检测多种数据校验和无法捕捉到的失效模式非常有效。包括:

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

    这包括操作系统排序规则的比较规则变化导致的问题。 text之类的可排序类型数据的比较必须是不变的(正如用于B-树索引扫描的所有比较必须不变一样),这意味着操作系统排序规则必须保持不变。 但是在很少的情况下,操作系统排序规则的更新会导致这些问题。 更常见的,主服务器和后备服务器之间排序顺序的不一致会相互牵连,这可能是因为使用的操作系统版本不一致。 这类不一致通常仅出现在后备服务器上,因此通常也仅能在后备服务器上检测到。

    如果这类问题出现,则它可能不会影响使用受影响排序规则排序的每一个索引,其原因是被索引值可能正好具有与行为不一致无关的相同的绝对顺序。关于PostgreSQL如何使用操作系统locale和排序规则的进一步细节请参考第 24.1 节第 24.2 节

  • 索引和被索引的对关系之间的结构不一致(在执行heapallindexed验证时)。

    在普通操作时没有将索引针对其对关系进行交叉检查。堆损坏的症状可能是很微妙的。

  • 由于底层PostgreSQL访问方法代码、排序代码或者事务管理代码中(假想的)未发现的缺陷导致的损坏。

    在测试可能引入逻辑不一致的PostgreSQL新特性或者被提议的特性时,索引的结构完整性自动验证扮演了重要角色。表结构、相关的可见性和事务状态信息的验证扮演了类似的角色。一种显而易见的测试策略是在运行标准回归测试时持续地调用amcheck函数。运行这些测试的详情请参考第 33.1 节

  • 正巧没有开启校验和的文件系统或者存储子系统故障。

    注意,如果在访问块时仅有一次共享缓存命中,验证时amcheck会在检查表示在某个共享内存缓冲区中的页面。因此,amcheck没有必要在验证时检查从文件系统读出的数据。注意当校验和被启用时,如果一个损坏的块被读取到缓冲区中,amcheck可能会由于校验和失效而产生错误。

  • 有缺陷的RAM或者内存子系统导致的损坏。

    PostgreSQL无法提供针对可更正内存错误的保护并且它假定用户使用的是具有工业标准纠错码(ECC)或更好保护技术的RAM。不过,ECC内存通常只能免疫单个位错误,并且不应该假定它能提供对导致内存损坏失效的绝对保护。

    在执行heapallindexed验证时,通常有大幅增加的机会可以检测单个位错误,因为会测试严格的二元等值并且会在堆中测试被索引属性。

由于有故障的存储硬件,或者相关文件被不相关的软件覆盖或修改,可能会发生结构损坏。 这类损坏也可以通过data page checksums来检测。

格式正确、内部一致并且相对于其内部校验和正确的关系页依然可能包含逻辑损坏。 因此,这类损坏不能被checksums所检测到。 例如包括主表中的toasted值在toast表中缺少相应的条目,以及主表中具有比数据库或集群中最古老的有效Transaction ID更旧的Transaction ID的元组。

在生产系统中已经观察到多个导致逻辑损坏的原因,包括PostgreSQL服务器软件中的缺陷、错误且考虑欠妥的备份和恢复工具,以及用户错误。

在实时生产环境中,损坏关系是最令人担忧的,而这样的环境中是最不欢迎高风险活动的。 基于此原因,设计了verify_heapam以在无过度风险的情况下诊断错误。 它不能保证防止后端崩溃的所有原因,因为在严重损坏的系统上,即使执行调用查询也可能不安全。 执行对catalog tables的访问,如果编目自身损坏了,也可能会出现问题。

通常,amcheck仅能证明损坏的存在,但它无法证明损坏不存在。

F.2.4. 修复损坏

amcheck没有产生与损坏相关的错误绝不应该被当做假阳性。amcheck会在(定义上)应该绝不会发生的情况中抛出错误,因此常常需要对amcheck错误进行仔细地分析。

对于amcheck检测到的问题没有一般性的修复方法。应该寻找产生不变条件违背的根本原因。在诊断amcheck检测到的损坏时,pageinspect可能会扮演一个非常有用的角色。REINDEX在修复损坏过程中可能无法起到效果。