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

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