amcheck模块提供了一组函数,可用于验证关系结构的逻辑一致性。如果结构看起来有效,就不会引发错误。
这些函数会验证特定关系表示结构中的多种不变式。 索引扫描以及其他重要操作背后的访问方法函数是否正确,依赖于这些不变式始终成立。 例如,某些函数除其他事项外,还会验证所有 B-树页面中的项都按“逻辑”顺序排列 (例如,对于text上的 B-树索引,索引元组应当按排序规则定义的词法顺序排列)。 如果这种特定不变式由于某种原因不再成立,那么受影响页面上的二分查找就可能错误地引导索引扫描, 从而使 SQL 查询返回错误答案。
验证工作使用的过程与索引扫描本身所使用的过程相同,而这些过程可能是用户定义的操作符类代码。 例如,B-树索引验证依赖于一个或多个 B-树支持函数 1 例程执行的比较。 关于操作符类支持函数的细节,见Section 37.16.3。
只有超级用户才能使用amcheck函数。
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)
本例展示了一个会话,用于验证最大的 10 个系统目录索引,这些索引位于数据库“test”中。对其中属于唯一索引的那一部分,还要求验证所有堆元组在索引中都有对应的索引元组。由于没有引发错误,因此所有受测索引看起来都具有逻辑一致性。当然,也很容易修改这个查询,使其调用bt_index_check来验证数据库中每一个支持验证的索引。
bt_index_check会在目标索引及其所属的堆关系上获取 AccessShareLock。这种锁模式与简单 SELECT语句在关系上获取的锁模式相同。 bt_index_check不会验证跨越父子关系的不变式, 但如果heapallindexed为true, 它会验证所有堆元组在索引中都有对应的索引元组。 当在线生产环境中需要一种例行的、轻量级的损坏检查时, 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会话中于运行验证查询之前执行:
SET client_min_messages = DEBUG1;
则会以一个便于掌控的细节层次显示关于验证进度的消息。
heapallindexed验证当验证函数的heapallindexed参数为 true时,会针对与目标索引关系相关联的表执行一个额外的验证阶段。 这一阶段由一次“伪”CREATE INDEX操作构成, 它会借助一个临时的、位于内存中的汇总结构,检查所有假想的新索引元组是否存在。 这个汇总结构会在基本验证第一阶段中按需构建,并为目标索引中找到的每个元组生成“指纹”。 heapallindexed验证背后的总体原则是:一个与现有目标索引等价的新索引, 只能包含那些可以在现有结构中找到的项。
额外的heapallindexed阶段会带来显著开销: 验证通常需要耗费数倍于平常的时间。不过,执行heapallindexed验证时, 所获取的关系级锁并不会发生变化。
这个汇总结构的大小受maintenance_work_mem限制。 为了确保对每个本应在索引中有表示的堆元组而言,漏检一处不一致的概率不超过 2%, 每个元组大约需要 2 字节内存。为每个元组提供的内存越少,漏掉不一致的概率就会缓慢上升。 这种方法显著限制了验证开销,同时只会轻微降低发现问题的概率, 对那些将验证视为例行维护任务的部署环境尤其如此。 每次重新执行验证时,任何单个缺失或格式错误的元组都会再次获得被检测到的机会。
amcheckamcheck能够有效检测出多种数据页校验和始终无法捕捉的故障模式,包括:
由操作符类实现错误导致的结构不一致。
这也包括因操作系统排序规则的比较规则发生变化而引起的问题。 像text这类可排序类型的数据值之间的比较必须是不可变的 (正如用于 B-树索引扫描的所有比较都必须不可变一样), 这就意味着操作系统排序规则绝不能发生变化。 虽然这种情况比较少见,但操作系统排序规则的更新确实可能导致此类问题。 更常见的是主库与备库之间的排序顺序不一致, 这可能是因为两边使用的操作系统主版本不同。 这类不一致通常只会出现在备库上,因此通常也只能在备库上检测到。
如果出现此类问题,它未必会影响每一个按受影响排序规则排序的索引, 因为被索引的值也可能恰好在行为不一致的情况下仍具有相同的绝对顺序。 关于PostgreSQL如何使用操作系统区域设置和排序规则的更多细节, 见Section 23.1和Section 23.2。
索引与其所索引的堆关系之间的结构不一致 (在执行heapallindexed验证时)。
在正常运行期间,索引并不会与其堆关系进行交叉核对。 堆损坏的症状可能十分隐蔽。
假设存在的、底层PostgreSQL访问方法代码、 排序代码或事务管理代码中尚未发现的缺陷所导致的损坏。
对索引结构完整性的自动验证,在对新的或拟议中的 PostgreSQL特性进行一般性测试时具有作用, 因为这些特性完全可能引入逻辑不一致。 对表结构以及相关可见性、事务状态信息的验证,也起着类似作用。 一种显而易见的测试策略,是在运行标准回归测试时持续调用 amcheck函数。 关于如何运行测试,见Section 32.1。
在禁用数据校验和时,由文件系统或存储子系统故障造成的损坏。
请注意,如果访问某个块时只是命中了共享缓冲区, 那么amcheck检查的是验证时该页面在某个共享内存缓冲区中的表示。 因此,amcheck并不一定会检查验证时从文件系统读入的数据。 另请注意,当启用了校验和时,如果某个损坏块被读入缓冲区, amcheck可能会因为校验和失败而引发错误。
由有缺陷的 RAM 或更广义的内存子系统导致的损坏。
PostgreSQL并不防御可纠正的内存错误, 并且假定所用 RAM 采用业界标准的纠错码(ECC)或更强的保护机制。 然而,ECC 内存通常只对单比特错误有效, 不应被视为能对导致内存损坏的故障提供绝对保护。
执行heapallindexed验证时, 由于会测试严格的二进制相等性,并且还会检查堆中的被索引属性, 因而检测出单比特错误的机会通常会大大增加。
一般来说,amcheck只能证明损坏存在,无法证明损坏不存在。
请注意,如果访问某个块时只是命中了共享缓冲区, 那么amcheck检查的是验证时该页面在某个共享内存缓冲区中的表示。 因此,amcheck并不一定会检查验证时从文件系统读入的数据。 另请注意,当启用了校验和时,如果某个损坏块被读入缓冲区, amcheck可能会因为校验和失败而引发错误。
对于amcheck检测到的问题,并不存在通用的修复方法。 应当查明导致不变式遭到破坏的根本原因。 在诊断amcheck检测到的损坏时, pageinspect可能会发挥有用作用。 REINDEX未必能够有效修复损坏。