VM
观测 Visibility Map 置位 / 清位,以及 Index Only Scan 是否因此免访堆。机制见 Why & How: VM;计划形态见 Index Only Scan。
- 调试:
VACUUM tb前后EXPLAIN (ANALYZE) SELECT a FROM tb WHERE a = 5432的 Heap Fetches - 依赖:contrib
pg_visibility - 表上
autovacuum_enabled = off,单会话、不要另开未提交事务(oldest xmin 过老则无法标 all-visible)
Case
CREATE EXTENSION IF NOT EXISTS pg_visibility;
DROP TABLE IF EXISTS tb;
CREATE TABLE tb (a int, b bigint) WITH (autovacuum_enabled = off);
INSERT INTO tb SELECT n, n FROM generate_series(1, 100000) AS n;
CREATE INDEX idx ON tb (a);
ANALYZE tb;
数据已提交,VACUUM 未跑,VM 仍为 0(保守)。
postgres=# SELECT pg_relation_filepath('tb') AS main, pg_relation_filepath('tb') || '_vm' AS vm_fork;
main | vm_fork
--------------+-----------------
base/5/24702 | base/5/24702_vm
postgres=# SELECT * FROM pg_visibility_map_summary('tb');
all_visible | all_frozen
-------------+------------
0 | 0
VACUUM 前:访问堆
postgres=# SET enable_seqscan = off;
postgres=# EXPLAIN (ANALYZE, COSTS OFF) SELECT a FROM tb WHERE a = 5432;
QUERY PLAN
---------------------------------------------------------------------------
Index Only Scan using idx on tb (actual time=0.041..0.043 rows=1 loops=1)
Index Cond: (a = 5432)
Heap Fetches: 1
Planning Time: 0.292 ms
Execution Time: 0.084 ms
VACUUM 后:index only scan
postgres=# VACUUM (VERBOSE) tb;
...
pages: 0 removed, 541 remain, 541 scanned (100.00% of total)
...
postgres=# SELECT * FROM pg_visibility_map_summary('tb');
all_visible | all_frozen
-------------+------------
541 | 0
postgres=# SELECT blkno, all_visible, all_frozen, pd_all_visible FROM pg_visibility('tb') LIMIT 2;
blkno | all_visible | all_frozen | pd_all_visible
-------+-------------+------------+----------------
0 | t | f | t
1 | t | f | t
-- VM all-visible 与堆页 PD_ALL_VISIBLE 应一致
postgres=# EXPLAIN (ANALYZE, COSTS OFF) SELECT a FROM tb WHERE a = 5432;
QUERY PLAN
---------------------------------------------------------------------------
Index Only Scan using idx on tb (actual time=0.013..0.014 rows=1 loops=1)
Index Cond: (a = 5432)
Heap Fetches: 0
Planning Time: 0.106 ms
Execution Time: 0.035 ms
FREEZE:all-frozen
postgres=# VACUUM (VERBOSE, FREEZE) tb;
postgres=# SELECT * FROM pg_visibility_map_summary('tb');
all_visible | all_frozen
-------------+------------
541 | 541
调用链
VACUUM 置位(持堆页锁):
VACUUM 扫堆页、必要时 freeze
-> 确认全可见 / 已冻结
-> visibilitymap_pin(无堆锁,可 I/O)
-> 锁堆页、复核
-> 置 PD_ALL_VISIBLE
-> visibilitymap_set → log_heap_visible
DML 清位(与堆页修改同一临界区):
heap_insert / heap_update / heap_delete / heap_lock_tuple
看堆页(尚未加锁):若 PD_ALL_VISIBLE → visibilitymap_pin
锁堆页
若其间 VACUUM 刚置位而 VM 未 pin → 放锁、pin、再锁
改 tuple + XLogInsert
同一临界区 visibilitymap_clear
Index Only Scan:
ExecIndexOnlyScan | ... | IndexOnlyNext
tid = index_getnext_tid(scandesc, direction)
visibilitymap_get_status
all-visible -> 不再访堆(仍持有索引叶 pin)
否则 heap fetch + HeapTupleSatisfiesVisibility