槽位 1 上的 HEAP_HOT_UPDATED 和槽位 4 上的 HEAP_ONLY_TUPLE 表明这是一次 HOT 更新;下面的 HOT 一节会解释这种更新。
动手验证 · lab 05-mvcc
selectlab.prove('the old version is still there, stamped with the updater''s xid and pointing at (0,4)',(selectt_xmaxfrompage0wherelp=1)=(selectt_xminfrompage0wherelp=4)and(selectt_ctidfrompage0wherelp=1)='(0,4)');selectlab.prove('HEAP_HOT_UPDATED on the old version and HEAP_ONLY_TUPLE on the new: a HOT update','HEAP_HOT_UPDATED'=any((selectraw_flagsfrompage0wherelp=1)::text[])and'HEAP_ONLY_TUPLE'=any((selectraw_flagsfrompage0wherelp=4)::text[]));
selectlab.prove('incrementing a 4-byte counter in a 1 KB row writes a whole new 1 KB tuple',lp_len>1000)fromheap_page_items(get_raw_page('wide_counter',(:'wide_ctid'::text::point)[0]::int))wherelp=(:'wide_ctid'::text::point)[1]::int;selectlab.prove('but the WAL record for a same-page update logs only the changed bytes (under 100)',bytes<100)fromwide_wal;
selectlab.prove('after vacuum, lp 1 is a redirect (lp_flags 2) to slot 6',lp_flags=2andlp_off=6)frompage0wherelp=1;selectlab.prove('lp 4 and 5 are unused (lp_flags 0), free for the next insert or update',count(*)=2andbool_and(lp_flags=0))frompage0wherelpin(4,5);
selectlab.prove('account 2 now has two primary key entries: the update changed an indexed column, so it was not HOT',array_agg(ctid::textorderbyitemoffset)='{"(0,2)","(0,4)"}')frombt_page_items('account_usage_pkey',1)wheredata='02 00 00 00 00 00 00 00';selectlab.prove('fillfactor 100: 0 of 10,000 updates were HOT, and the table grew from 1,126 to 1,239 pages',s.n_tup_upd=10000ands.n_tup_hot_upd=0andpg_relation_size('users_ff100')/8192between1200and1280)frompg_stat_user_tablesswhererelname='users_ff100';selectlab.prove('fillfactor 85: all 10,000 updates were HOT, and the table did not grow (1,322 pages)',s.n_tup_upd=10000ands.n_tup_hot_upd=10000andpg_relation_size('users_ff85')/8192=1322)frompg_stat_user_tablesswhererelname='users_ff85';
selectlab.prove('session B still sees the old version at (0,3), with A''s xid already in its xmax',ctid='(0,3)'andxmax::text=:'a_xid'andevents_today=0)fromaccount_usagewhereaccount_id=3;selectlab.prove('pg_xact_status says A is in progress',pg_xact_status(:'a_xid'::xid8)='in progress');
selectlab.prove('a freshly loaded tuple has no HEAP_XMIN_COMMITTED hint yet',not('HEAP_XMIN_COMMITTED'=any(raw_flags)))fromheap_page_items(get_raw_page('hint_demo2',100)),heap_tuple_infomask_flags(t_infomask,t_infomask2)wherelp=1;selectlab.prove('after a plain count(*), the tuple carries HEAP_XMIN_COMMITTED: a read modified the page','HEAP_XMIN_COMMITTED'=any(raw_flags))fromheap_page_items(get_raw_page('hint_demo2',100)),heap_tuple_infomask_flags(t_infomask,t_infomask2)wherelp=1;
begin;selectpg_current_xact_id_if_assigned();-- nothing yet
selectcount(*)fromaccounts;-- still nothing: reads don't need an xid
selectpg_current_xact_id_if_assigned();insertintoaccount_usage(account_id)values(4);selectpg_current_xact_id_if_assigned();-- 869595, assigned by the first write
commit;
selectlab.prove('still no xid after a read: reads don''t need one',pg_current_xact_id_if_assigned()isnull);selectlab.prove('the deleted row stays on the page at full size, with an xmax and HEAP_KEYS_UPDATED',lp_flags=1andlp_len>0andt_xmax::text<>'0'andt_ctid=:'deleted_ctid'::tidand'HEAP_KEYS_UPDATED'=any(raw_flags))frompage0wherelp=(:'deleted_ctid'::tid::text::point)[1]::int;
selectlab.prove('an older REPEATABLE READ snapshot sees the truncated table as empty',(selectnfromdblink('session_a','select count(*) from imports')ast(nbigint))=0);selectlab.prove('but still sees all 500 deleted rows',(selectnfromdblink('session_a','select count(*) from deletes_demo')ast(nbigint))=500);
selectlab.prove('after updating every row: about 18 MB, 100,000 live and 100,000 dead tuples',table_lenbetween18000000and19000000andtuple_count=100000anddead_tuple_count=100000)frompgstattuple('users');selectlab.prove('after vacuum: no dead tuples, still about 18 MB, about half of it free space',dead_tuple_count=0andtable_lenbetween18000000and19000000andfree_percentbetween45and55)frompgstattuple('users');selectlab.prove('the next full update reuses the space: still about 18 MB, not 27',pg_relation_size('users')between18000000and19000000);
selectlab.prove('vacuum removes nothing: the 50,000 deleted rows are still there, dead but not yet removable',dead_tuple_count=50000)frompgstattuple('users_ff100');selectlab.prove('once A commits, the same vacuum removes them',dead_tuple_count=0)frompgstattuple('users_ff100');selectlab.prove('and truncates the table from 1,239 to 1,126 pages',pg_relation_size('users_ff100')/8192=1126);
lp | combined_flags | committed | invalid
----+--------------------+-----------+---------
4 | {HEAP_XMIN_FROZEN} | t | t
5 | {HEAP_XMIN_FROZEN} | t | t
6 | {HEAP_XMIN_FROZEN} | t | t
selectlab.prove('after vacuum (freeze), every live tuple is HEAP_XMIN_FROZEN: committed and invalid together',count(*)=3andbool_and('HEAP_XMIN_FROZEN'=any(combined_flags))andbool_and('HEAP_XMIN_COMMITTED'=any(raw_flags)and'HEAP_XMIN_INVALID'=any(raw_flags)))frompage0wherelp_flags=1;selectlab.prove('the visibility map marks the page all-visible and all-frozen',all_visibleandall_frozen)frompg_visibility_map('account_usage')whereblkno=0;