Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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)是否允许
01是
00是
11是
10否

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