跳至内容
35.4 索引、collation 与 `amcheck`

35.4 索引、collation 与 `amcheck`

data checksum 可以证明 page 字节与页内 checksum 是否一致,却无法证明 B-tree 的 tuple 顺序、parent/child link、heap-to-index coverage 或比较规则仍然一致。amcheck 针对的是 relation 的结构与逻辑不变量,两者互补。

35.4.1 bt_index_checkheapallindexed 与锁成本

选择正确强度

安装 supplied extension:

CREATE EXTENSION IF NOT EXISTS amcheck;

单个 B-tree 轻量检查:

SELECT bt_index_check(
  index => 'public.orders_created_idx'::regclass,
  heapallindexed => false,
  checkunique => false
);

检查 heap tuple 是否都有 index 表示:

SELECT bt_index_check(
  index => 'public.orders_created_idx'::regclass,
  heapallindexed => true,
  checkunique => false
);

更全面的 parent/child 与 root descent:

SELECT bt_index_parent_check(
  index => 'public.orders_created_idx'::regclass,
  heapallindexed => true,
  rootdescend => true,
  checkunique => false
);

函数返回 void;没有抛错表示本次所检查的不变量未发现异常,不是“整个数据库完全 正确”。

锁与副本限制

PostgreSQL 18 官方边界:

函数relation lock特点
bt_index_checkindex + heap AccessShareLock较轻,可用于 hot standby
bt_index_parent_checkindex + heap ShareLock阻止 DML/VACUUM,不能用于 hot standby

heapallindexed=true 不提高 relation lock mode,但会显著增加时间、I/O 与内存工作; 其摘要结构受 maintenance_work_mem 约束。checkunique=true 又增加 unique visibility 检查。不要在事故主库上对所有大 index 一次性开最强选项。

一个风险排序:

exact reported index, heapallindexed=false
  -> exact index, heapallindexed=true on clone/low-load window
      -> parent check on writable clone or maintenance window
          -> wider object set by tablespace/provider/change scope

执行前记录 relation size、锁等待、statement timeout、I/O 预算、replica lag 与停止线。

verify_heapam

SELECT *
FROM verify_heapam(
  relation => 'public.orders'::regclass,
  on_error_stop => false,
  check_toast => true,
  skip => 'none',
  startblock => 0,
  endblock => 999
);

它可按 block 范围返回 heap/tuple 结构问题,但 check_toast=true 较慢;若依赖结构本身 损坏,检查也可能 error,极端情况下存在 crash 风险。优先在 clone 执行,并对输出做 隐私审查;错误信息虽偏结构,仍可能泄露数据特征。

35.4.2 collation 版本变化与索引顺序异常

版本 mismatch

SELECT n.nspname,
       c.collname,
       c.collprovider,
       c.collversion AS stored_version,
       pg_collation_actual_version(c.oid) AS actual_version,
       c.collversion IS DISTINCT FROM
         pg_collation_actual_version(c.oid) AS mismatch
FROM pg_collation AS c
JOIN pg_namespace AS n ON n.oid = c.collnamespace
WHERE c.collversion IS NOT NULL
ORDER BY mismatch DESC, 1, 2;

数据库 default collation:

SELECT datname,
       datcollversion AS stored_version,
       pg_database_collation_actual_version(oid) AS actual_version
FROM pg_database
ORDER BY datname;

provider:

c = libc
i = ICU
b = builtin

操作系统或 ICU 升级可能改变 text comparison。旧 index 是按旧规则构建的,新 backend 按新规则搜索时,binary search 可能走错方向并返回错误答案。primary/standby provider 版本不一致也可能让只读副本先暴露问题。

枚举 index 的 collation 依赖

SELECT i.indexrelid::regclass AS index_name,
       x.ord AS key_position,
       x.collation_oid::regcollation AS collation
FROM pg_index AS i
CROSS JOIN LATERAL
  unnest(i.indcollation) WITH ORDINALITY AS x(collation_oid, ord)
WHERE x.collation_oid <> 0
ORDER BY 3, 1, 2;

表达式 index、operator class、partition、materialized view 与 extension objects 还需结合 pg_depend、DDL 和应用查询盘点。indcollation=0 只表示该 index key 不使用 collation, 不是整个 object 无外部语义依赖。

修复顺序

在 provider 已稳定、工作副本验证后:

1. inventory every affected derived object
2. REINDEX / rebuild each object under intended comparison rules
3. run amcheck and business/order invariants
4. ALTER COLLATION ... REFRESH VERSION
   or ALTER DATABASE ... REFRESH COLLATION VERSION
5. repeat checks on every role/replica after rollout

REFRESH VERSION 更新 catalog 记录,不会自动重建所有依赖对象。先 refresh 会让 warning 消失,却可能留下按旧规则排列的 index,是典型“消除检测器而没有消除缺陷”。

本章实验只伪造 stored version,实际 ICU 规则没有变化;因此它只证明流程与元数据, 不证明真实升级后的每个 index 必然有序。

35.4.3 索引可重建不意味着堆表数据安全

先证明 source relation

REINDEX 从 heap 读取 row 并生成新派生结构。如果 heap page、TOAST、visibility/XID 或 业务数据已经错误,新 index 可能只是忠实地索引了错误 source

至少组合:

data checksum / verify_heapam on source
TOAST readability
row count and partition coverage
constraints and foreign-key validation
business aggregates/ledger/token
seqscan vs indexscan bounded equivalence
amcheck after rebuild
backup/replica cross-check

查询等价性要控制 snapshot

若比较 seqscan 与 indexscan:

same transaction snapshot
same WHERE/ORDER BY/collation
stable deterministic projection
bounded result
explicit NULL and duplicate handling
same role/GUC/RLS context

两个独立时间点的 count 不同,可能只是并发写,不是 index corruption。对生产主库最好 在 repeatable-read/read-only snapshot 或 clone 中比较。

REINDEX 的生产风险

普通 REINDEXREINDEX CONCURRENTLY 的锁、空间、WAL、失败状态和支持对象不同。 损坏场景下 concurrently 也未必是正确路线:它需要继续依赖当前系统结构和写入并发。 先在 clone 证明:

source readable
new index validates
temporary disk/WAL capacity sufficient
unique conflicts understood
cutover and rollback defined

如果坏的是 system catalog index、heap 或唯一可信 source,停止套用普通 REINDEX runbook,升级抢救流程。


上一节:页与 checksum 证据 · 返回本章目录 · 下一节:抽取、跳过与重建策略 · 查看全书目录 · 查看索引中心

最后更新于