Postgresql 中文操作指南

F.2. amcheck — tools to verify table and index consistency #

amcheck 模块提供了允许验证关系结构逻辑一致性的函数。

B 树检查函数验证了特定关系的表示结构中的各种 invariants。索引扫描和其他重要操作后访问方法函数的正确性依赖于始终维持这些不变量。例如,某些函数验证的所有内容中包括,所有 B 树页面按“逻辑”顺序包含项目(例如,对于 text 上的 B 树索引,索引元组应按对照词法顺序排列)。如果该特定不变量以某种方式无法维持,我们可以预期对受影响页面的二进制搜索会错误地引导索引扫描,从而导致 SQL 查询错误的答案。如果结构显示有效,则不引发错误。

验证使用与索引扫描本身使用的相同过程执行,该过程可以是用户定义的操作员类代码。例如,B 树索引验证依赖于与一个或多个 B 树支持函数 1 例程进行比较。有关操作员类支持函数的详细信息,请参见 Section 38.16.3

与通过引发错误来报告损坏的 B 树检查函数不同,堆检查函数 verify_heapam 检查一个表并尝试返回一组行,每检测到一个损坏返回一行。尽管如此,如果 verify_heapam 依赖的设施自身已损坏,则该函数可能无法继续,并且可能引发错误。

可以向非超级用户授予执行 amcheck 函数的权限,但在授予此类权限之前,应仔细考虑数据安全和隐私问题。尽管这些函数生成的损坏报告主要关注损坏数据的结构和发现的损坏性质,但少于关注损坏数据的内容,某个获得执行这些函数的权限的攻击者,特别是如果攻击者还可以引发损坏,可能能够从这样的消息中推断出数据本身的部分信息。

F.2.1. Functions #

  • 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)。这些锁定可防止来自 INSERTUPDATEDELETE 指令的并发数据修改。锁定还可防止底层关系被 VACUUM 以及所有其他实用程序命令同时处理。请注意,该函数仅在运行时持有锁定,而不是在整个事务中持有锁定。

  • bt_index_parent_check 的附加验证更有可能检测到各种病理案例。这些案例可能涉及索引使用的错误实现的 B 树操作员类,或者从假设来说,底层 B 树索引访问方法代码中未发现的错误。请注意,与 bt_index_check 不同,bt_index_parent_check 在启用热备用模式时(即在只读物理副本上)无法使用。

Tip

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

    • 检查表、序列或物化视图的结构损坏,其中关系中的页面包含格式无效的数据,以及逻辑损坏,其中页面在结构上有效,但与数据库群集的其余部分不一致。

    • 识别以下可选参数:

    • 对于检测到的每个损坏,verify_heapam 将返回一行,其中包含如下列:

  • on_error_stop

    • 如果为真,则损坏检查会在找到任何损坏的首个块的末尾停止。

    • Defaults to false.

  • check_toast

    • 如果为真,将根据目标关系的 TOAST 表检查已烤制的值。

    • 此选项已知很慢。此外,如果 TOAST 表或其索引已损坏,检查与 TOAST 值导致服务器崩溃,尽管在很多情况下只产生一个错误。

    • Defaults to false.

  • skip

    • 如果不为 none,损坏检查会跳过标记为全可见或全冻结的块,具体取决于给定的值。有效选项为 all-visibleall-frozennone

    • Defaults to none.

  • startblock

    • 如果指定,损坏检查将从所指定块开始,跳过所有之前块。在目标表中块范围之外指定 startblock 错误。

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

  • endblock

    • 如果指定,损坏检查将在所指定块结束,跳过剩余所有块。在目标表中块范围之外指定 endblock 错误。

    • 默认情况下,会检查所有块。

  • blkno

    • 包含损坏页的块号。

  • offnum

    • 损坏元组的 OffsetNumber。

  • attnum

    • 如果损坏只针对元组中的一个列,而不是整个元组,则为元组中损坏列的属性号。

  • msg

    • 描述检测到的问题的信息。

F.2.2. Optional heapallindexed Verification #

当 B-Tree 验证函数的 heapallindexed 参数为 true 时,将基于与目标索引关系关联的表执行附加验证阶段。此操作包括一个“虚拟”CREATE INDEX 操作,它会基于一个临时的内存总结结构(这是在验证的第一个基本阶段按需构建的)检查是否存在所有假设的新索引元组。总结结构会“指纹识别”目标索引中找到的每个元组。heapallindexed 验证背后的高级原理是,与现有的目标索引相当的新索引只允许包含可在现有结构中找到的项。

附加的 heapallindexed 阶段会增加显著的开销:验证通常会花费好几倍的时间。但是,执行 heapallindexed 验证时,不会更改已获取的关系级别锁定。

总结结构的大小受 maintenance_work_mem 约束。为了确保索引中应表示的每个堆元组,不存在不一致检测失败的概率超过 2%,每个元组需要大约 2 字节的内存。每个元组分配的内存越少,漏检不一致的概率会逐渐升高。这种方法显著限制了验证的开销,同时只略微降低了检测问题的概率,特别是对于将验证视为例行维护任务的安装。每个缺失或畸形的元组在每次新的验证尝试中都会有新的机会被检测到。

F.2.3. Using amcheck Effectively #

amcheck 可有效检测 data checksums 未能捕获的各种类型的故障模式。其中包括:

结构损坏可能是由于存储硬件出现故障,或者相关文件被无关软件覆盖或修改而发生的。这种损坏也可以用 data page checksums 检测到。

格式正确、内部一致且相对于其自身的内部校验和正确的关系页面仍然可能包含逻辑损坏。因此,使用校验和无法检测到这类损坏。示例包括主表中有烤面包的值,而烤面包表中缺少相对应的条目,以及带有事务 ID 的主表中的元组,该事务 ID 早于数据库或群集中最早的有效事务 ID。

在生产系统中观察到了多种逻辑损坏的原因,包括 PostgreSQL 服务器软件中的错误、错误且不合理的备份和恢复工具以及用户错误。

受损关系最令人担忧的是在实时生产环境中,而恰恰是在此类环境中,高风险活动最不受欢迎。出于这个原因,verify_heapam 旨在诊断故障,而无需承担过度的风险。它不能防止后端崩溃的所有原因,因为即使执行调用查询在严重损坏的系统上也可能是不可靠的。访问 catalog tables 会被执行,如果目录本身已损坏,可能会出现问题。

一般来说,amcheck 只能证明损坏的存在;它无法证明损坏的缺失。

F.2.4. Repairing Corruption #

amcheck 产生的任何与损坏有关的错误都不应该是误报。amcheck 在定义上永远不应该发生的条件下会产生错误,因此通常需要仔细分析 amcheck 错误。

没有解决 amcheck 检测到的问题的一般方法。应寻求关于不变量冲突的根本原因的解释。 pageinspect 可能在诊断 amcheck 检测到的故障时发挥有用作用。REINDEX 可能无法有效修复故障。