# 索引、collation 与 `amcheck`

LLMS 索引： [llms.txt](/llms.txt)

---

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

## 35.4.1 `bt_index_check`、`heapallindexed` 与锁成本 {#item-35-4-1}

### 选择正确强度

安装 supplied extension：

```sql
CREATE EXTENSION IF NOT EXISTS amcheck;
```

单个 B-tree 轻量检查：

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

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

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

更全面的 parent/child 与 root descent：

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

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

### 锁与副本限制

PostgreSQL 18 官方边界：

| 函数 | relation lock | 特点 |
|---|---|---|
| `bt_index_check` | index + heap `AccessShareLock` | 较轻，可用于 hot standby |
| `bt_index_parent_check` | index + heap `ShareLock` | 阻止 DML/VACUUM，不能用于 hot standby |

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

一个风险排序：

```text
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`

```sql
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 版本变化与索引顺序异常 {#item-35-4-2}

### 版本 mismatch

```sql
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：

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

provider：

```text
d = database default
c = libc
i = ICU
b = builtin
```

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

### 枚举 index 的 collation 依赖

```sql
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 已稳定、工作副本验证后：

```text
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 索引可重建不意味着堆表数据安全 {#item-35-4-3}

### 先证明 source relation

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

至少组合：

```text
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：

```text
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 的生产风险

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

```text
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 证据](../03/) · [返回本章目录](../) · [下一节：抽取、跳过与重建策略](../05/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
