amcheck 模块提供让你校验关系结构逻辑一致性的函数。
B 树检查函数校验特定关系表示结构中的各种不变量。索引扫描与其他重要操作背后的访问方法函数的正确性依赖于这些不变量始终成立。例如,某些函数会校验所有 B 树页面的条目处于“逻辑”顺序(例如对 text 上的 B 树索引,索引元组应按整理后的字典序排列)。如果该不变量因故不成立,受影响页面上的二分搜索会错误地引导索引扫描,导致 SQL 查询返回错误结果。如果结构看起来有效,则不会抛出错误。运行这些检查函数期间,search_path 会被临时更改为 pg_catalog,
pg_temp。
校验使用与索引扫描自身相同的过程执行,其中可能包含用户定义的操作符类代码。例如,B 树索引校验依赖由一个或多个 B 树支持函数 1 例程完成的比较。操作符类支持函数的细节请参见 Section 36.16.3。
与通过抛出错误来报告损坏的 B 树检查函数不同,堆检查函数 verify_heapam 检查一张表并尝试返回一组行,每检测到一处损坏返回一行。尽管如此,如果 verify_heapam 所依赖的设施自身已损坏,该函数可能无法继续运行,转而抛出错误。
amcheck 函数的执行权限可以授予非超级用户,但在授予此类权限之前应仔细考虑数据安全与隐私问题。虽然这些函数生成的损坏报告关注的是数据的结构以及所发现损坏的性质,损坏数据的内容并非重点,但获得执行这些函数权限的攻击者,尤其是在攻击者还能诱发损坏的情况下,也许能从这类消息中推断出数据本身的一些内容。
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 不校验跨越子/父关系的不变量,但当 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 树索引)是否满足各种不变量。当 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 树操作符类,或者假设性地涉及底层 B 树索引访问方法代码中未发现的缺陷。请注意,与 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如果为真,损坏检查在发现任何损坏的第一个块结束后停止。
默认为 false。
check_toast如果为真,会针对目标关系的 TOAST 表检查被外置的值。
该选项以慢著称。此外,如果 toast 表或其索引已损坏,对照 toast 值检查有可能使服务器崩溃,尽管在许多情况下只会产生一个错误。
默认为 false。
skip
如果不为 none,损坏检查按指定跳过被标记为全可见或全冻结的块。有效选项为 all-visible、all-frozen 与 none。
默认为 none。
startblock
如果指定,损坏检查从指定块开始,跳过之前所有块。指定的 startblock 超出目标表的块范围是错误的。
默认从第一个块开始检查。
endblock
如果指定,损坏检查在指定块结束,跳过其余所有块。指定的 endblock 超出目标表的块范围是错误的。
默认检查所有块。
对于检测到的每处损坏,verify_heapam 返回一行,包含以下列:
blkno包含损坏页面的块号。
offnum损坏元组的 OffsetNumber。
attnum元组中损坏列的属性编号(如果损坏特定于某一列,而不是整个元组)。
msg描述所发现问题的消息。
heapallindexed 校验 #
当 B 树校验函数的 heapallindexed 参数为 true 时,会对与目标索引关系关联的表执行一个附加的校验阶段。这由一个“模拟”的 CREATE INDEX CONCURRENTLY 操作组成,它对照一个临时的、内存中的汇总结构检查所有假想的新索引元组的存在性(该结构在基本的第一阶段校验期间按需构建)。汇总结构对目标索引中发现的每个元组进行“指纹”记录。heapallindexed 校验背后的高层原理是:与现有目标索引等效的新索引只能包含能在现有结构中找到的条目。
附加的 heapallindexed 阶段带来显著的开销:校验通常需要花费数倍的时间。不过,执行 heapallindexed 校验时获取的关系级锁没有变化。
汇总结构的大小受 maintenance_work_mem 约束。为了确保对每个应当出现在索引中的堆元组,检测不到不一致的概率不超过 2%,每个元组大约需要 2 字节内存。每元组可用内存越少,漏检不一致的概率会缓慢上升。这种方法显著限制了校验的开销,同时只是轻微降低了检测出问题的概率,对把校验当作例行维护任务的部署尤其如此。任何单个缺失或畸形的元组都会在每次新的校验尝试中获得新的被检测到的机会。
amcheck #
amcheck 能够有效检测多种 数据校验和 无法发现的故障模式,包括:
由错误的操作符类实现导致的结构不一致。
这包括由操作系统排序规则的比较规则发生变化引起的问题。可排序类型(如 text)的数据比较必须不可变(正如 B 树索引扫描使用的所有比较必须不可变一样),这意味着操作系统的排序规则绝不能变化。虽然罕见,但操作系统排序规则的更新确实可能造成这些问题。更常见的情况是主服务器与备服务器之间排序顺序不一致,可能是因为两者使用的操作系<wbr></wbr>统大版本不一致。这类不一致通常只出现在备服务器上,因此通常只能在备服务器上检测到。
如果出现这种问题,它未必影响使用受影响排序规则排序的每一个索引,原因很简单:被索引的值碰巧可能无论行为是否不一致都具有相同的绝对顺序。关于 PostgreSQL 如何使用操作系统区域设置与排序规则的更多细节,请参见 Section 23.1 与 Section 23.2。
索引与其被索引堆关系之间的结构不一致(执行 heapallindexed 校验时)。
正常运行期间不存在索引与其堆关系之间的交叉检查。堆损坏的症状可能很微妙。
由假设性未发现的 PostgreSQL 底层访问方法代码、排序代码或事务管理代码缺陷导致的损坏。
自动校验索引的结构完整性,在测试可能引入逻辑不一致的新提出或已提出的 PostgreSQL 特性方面发挥作用。校验表结构以及关联的可见性与事务状态信息也起类似作用。一个显而易见的测试策略是在运行标准回归测试期间持续调用 amcheck 函数。运行测试的细节参见 Section 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 对修复损坏未必有效。