Visibility Map
lazy_scan_skip
visibilitymap_get_status
1. 定义
Visibility Map(VM):堆关系的独立 fork(<relfilenode>_vm,VISIBILITYMAP_FORKNUM),按 堆页 记两个 hint 位:
| bit | 常量 | 含义 |
|---|---|---|
| 低 | VISIBILITYMAP_ALL_VISIBLE | 该页所有 tuple 对所有事务可见 → 无 dead tuple,VACUUM 可跳过(普通清理) |
| 高 | VISIBILITYMAP_ALL_FROZEN | 该页所有 tuple 已 freeze → 防 wraparound 的整表 VACUUM 也可跳过 |
- 只服务 heap(及 toast / matview 等堆关系);索引没有 VM。
- 保守:bit 置 1 ⇒ 条件成立;bit 为 0 ⇒ 不知道(可能已经全可见,只是还没扫到)。
all-frozen只能在已经all-visible时置位;冻结流程见 Freeze。
2. 一致性
- 堆页头
PD_ALL_VISIBLE:改这一页的进程已经 pin 了堆页,看这个 bit 决定「要不要去清 VM」。 - VM fork 上对应的 all-visible:Index Only Scan / VACUUM 不读堆页时用。
DML 先看堆页标志再决定清不清 VM。因此只允许一种暂时不一致:堆页已经 PD_ALL_VISIBLE、VM 还是 0(保守,IOS 多访一次堆)。禁止反过来:VM=1 而堆页标志=0 —— 下次 INSERT/UPDATE 不会去 visibilitymap_clear,IOS 仍当全可见、跳过堆,可能读到错行。
这就是不变量(transam README · 提示):
VM all-visible = 1 ⇒ 堆页必须已有 PD_ALL_VISIBLE。
| VM-bit(_vm) | PD_ALL_VISIBLE(page) | 是否允许 |
|---|---|---|
| 0 | 1 | 是 |
| 0 | 0 | 是 |
| 1 | 1 | 是 |
| 1 | 0 | 否 |
3. 操作和使用
VACUUM 置位(持有堆页锁,防止刚看完「全可见」就被别人改页):
VACUUM 扫堆页、必要时 freeze
-> 确认全可见 / 已冻结
-> 先 pin VM(无堆锁)
-> 锁堆页、复核
-> 置 PD_ALL_VISIBLE
-> visibilitymap_set(WAL)
DML 清位(与堆页修改同一临界区,crash-safe):
看堆页(尚未加锁):若 PD_ALL_VISIBLE → 先 pin VM
锁堆页
若其间 VACUUM 刚置了 PD_ALL_VISIBLE 而 VM 未 pin
-> 放锁、pin、再锁
改 tuple + XLogInsert
同一临界区 visibilitymap_clear
Index Only Scan(nodeIndexonlyscan.c):
索引取 TID
-> visibilitymap_get_status
all-visible -> 不再访堆(仍持有索引叶 pin,避免 TID 回收竞态)
否则 heap fetch + HeapTupleSatisfiesVisibility