diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index e29c5ad086..3db59db26c 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -6484,7 +6484,8 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, * aborted or crashed then it's okay to ignore it, otherwise not. * Note that an updater older than cutoff_xid cannot possibly be * committed, because HeapTupleSatisfiesVacuum would have returned - * HEAPTUPLE_DEAD and we would not be trying to freeze the tuple. + * HEAPTUPLE_DEAD or HEAPTUPLE_RECENTLY_DEAD and we would not be + * trying to freeze the tuple. * * As with all tuple visibility routines, it's critical to test * TransactionIdIsInProgress before TransactionIdDidCommit, @@ -6514,8 +6515,9 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, */ /* - * Since the tuple wasn't marked HEAPTUPLE_DEAD by vacuum, the - * update Xid cannot possibly be older than the xid cutoff. + * Since the tuple wasn't marked HEAPTUPLE_DEAD or + * HEAPTUPLE_RECENTLY_DEAD by vacuum, the update Xid cannot + * possibly be older than the xid cutoff. */ Assert(!TransactionIdIsValid(update_xid) || !TransactionIdPrecedes(update_xid, cutoff_xid)); @@ -6597,8 +6599,9 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, * Caller is responsible for setting the offset field, if appropriate. * * It is assumed that the caller has checked the tuple with - * HeapTupleSatisfiesVacuum() and determined that it is not HEAPTUPLE_DEAD - * (else we should be removing the tuple, not freezing it). + * HeapTupleSatisfiesVacuum() and determined that it is not HEAPTUPLE_DEAD, + * in which case we should be removing the tuple, not freezing it, or + * HEAPTUPLE_RECENTLY_DEAD, which would be removed by the next pruning. * * NB: cutoff_xid *must* be <= the current global xmin, to ensure that any * XID older than it could neither be running nor seen as running by any diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c index 45b1859475..da1d2bd557 100644 --- a/src/backend/commands/vacuumlazy.c +++ b/src/backend/commands/vacuumlazy.c @@ -593,6 +593,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, OffsetNumber offnum, maxoff; bool tupgone, + tupkeep, hastup; int prev_dead_count; int nfrozen; @@ -974,6 +975,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, tuple.t_tableOid = RelationGetRelid(onerel); tupgone = false; + tupkeep = false; switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf)) { @@ -992,11 +994,16 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, * it were RECENTLY_DEAD. Also, if it's a heap-only * tuple, we choose to keep it, because it'll be a lot * cheaper to get rid of it in the next pruning pass than - * to treat it like an indexed tuple. + * to treat it like an indexed tuple. Note that the + * tuple cannot be frozen as well as it cannot become + * visible. */ if (HeapTupleIsHotUpdated(&tuple) || HeapTupleIsHeapOnly(&tuple)) + { nkeep += 1; + tupkeep = true; + } else tupgone = true; /* we can delete the tuple */ all_visible = false; @@ -1047,9 +1054,11 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, /* * If tuple is recently deleted then we must not remove it - * from relation. + * from relation. It cannot be frozen as well as it should + * not become visible. */ nkeep += 1; + tupkeep = true; all_visible = false; break; case HEAPTUPLE_INSERT_IN_PROGRESS: @@ -1081,16 +1090,21 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, hastup = true; /* - * Each non-removable tuple must be checked to see if it needs - * freezing. Note we already have exclusive buffer lock. + * Each non-removable tuple that we do not keep must be checked + * to see if it needs freezing. Note we already have exclusive + * buffer lock. */ - if (heap_prepare_freeze_tuple(tuple.t_data, FreezeLimit, - MultiXactCutoff, &frozen[nfrozen], - &tuple_totally_frozen)) - frozen[nfrozen++].offset = offnum; - - if (!tuple_totally_frozen) - all_frozen = false; + if (!tupkeep) + { + if (heap_prepare_freeze_tuple(tuple.t_data, FreezeLimit, + MultiXactCutoff, + &frozen[nfrozen], + &tuple_totally_frozen)) + frozen[nfrozen++].offset = offnum; + + if (!tuple_totally_frozen) + all_frozen = false; + } } } /* scan along page */ diff --git a/src/test/isolation/expected/multixact-freeze.out b/src/test/isolation/expected/multixact-freeze.out new file mode 100644 index 0000000000..dd045613f9 --- /dev/null +++ b/src/test/isolation/expected/multixact-freeze.out @@ -0,0 +1,101 @@ +Parsed test spec with 2 sessions + +starting permutation: s1_update s1_commit s1_vacuum s2_key_share s2_commit +step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3; +step s1_commit: COMMIT; +step s1_vacuum: VACUUM FREEZE tab_freeze; +step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE; +id + +3 +step s2_commit: COMMIT; + +starting permutation: s1_update s1_commit s2_key_share s1_vacuum s2_commit +step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3; +step s1_commit: COMMIT; +step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE; +id + +3 +step s1_vacuum: VACUUM FREEZE tab_freeze; +step s2_commit: COMMIT; + +starting permutation: s1_update s1_commit s2_key_share s2_commit s1_vacuum +step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3; +step s1_commit: COMMIT; +step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE; +id + +3 +step s2_commit: COMMIT; +step s1_vacuum: VACUUM FREEZE tab_freeze; + +starting permutation: s1_update s2_key_share s1_commit s1_vacuum s2_commit +step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3; +step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE; +id + +3 +step s1_commit: COMMIT; +step s1_vacuum: VACUUM FREEZE tab_freeze; +step s2_commit: COMMIT; + +starting permutation: s1_update s2_key_share s1_commit s2_commit s1_vacuum +step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3; +step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE; +id + +3 +step s1_commit: COMMIT; +step s2_commit: COMMIT; +step s1_vacuum: VACUUM FREEZE tab_freeze; + +starting permutation: s1_update s2_key_share s2_commit s1_commit s1_vacuum +step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3; +step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE; +id + +3 +step s2_commit: COMMIT; +step s1_commit: COMMIT; +step s1_vacuum: VACUUM FREEZE tab_freeze; + +starting permutation: s2_key_share s1_update s1_commit s1_vacuum s2_commit +step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE; +id + +3 +step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3; +step s1_commit: COMMIT; +step s1_vacuum: VACUUM FREEZE tab_freeze; +step s2_commit: COMMIT; + +starting permutation: s2_key_share s1_update s1_commit s2_commit s1_vacuum +step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE; +id + +3 +step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3; +step s1_commit: COMMIT; +step s2_commit: COMMIT; +step s1_vacuum: VACUUM FREEZE tab_freeze; + +starting permutation: s2_key_share s1_update s2_commit s1_commit s1_vacuum +step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE; +id + +3 +step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3; +step s2_commit: COMMIT; +step s1_commit: COMMIT; +step s1_vacuum: VACUUM FREEZE tab_freeze; + +starting permutation: s2_key_share s2_commit s1_update s1_commit s1_vacuum +step s2_key_share: SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE; +id + +3 +step s2_commit: COMMIT; +step s1_update: UPDATE tab_freeze SET x = x + 1 WHERE id = 3; +step s1_commit: COMMIT; +step s1_vacuum: VACUUM FREEZE tab_freeze; diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index 32c965b2a0..98208d2fdc 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -36,6 +36,7 @@ test: insert-conflict-toast test: delete-abort-savept test: delete-abort-savept-2 test: aborted-keyrevoke +test: multixact-freeze test: multixact-no-deadlock test: multixact-no-forget test: lock-committed-update diff --git a/src/test/isolation/specs/multixact-freeze.spec b/src/test/isolation/specs/multixact-freeze.spec new file mode 100644 index 0000000000..3cd9965b2f --- /dev/null +++ b/src/test/isolation/specs/multixact-freeze.spec @@ -0,0 +1,27 @@ +# Test for interactions of tuple freezing with dead, as well as recently-dead +# tuples using multixacts via FOR KEY SHARE. +setup +{ + CREATE TABLE tab_freeze ( + id int PRIMARY KEY, + name char(3), + x int); + INSERT INTO tab_freeze VALUES (1, '111', 0); + INSERT INTO tab_freeze VALUES (3, '333', 0); +} + +teardown +{ + DROP TABLE tab_freeze; +} + +session "s1" +setup { BEGIN; } +step "s1_update" { UPDATE tab_freeze SET x = x + 1 WHERE id = 3; } +step "s1_commit" { COMMIT; } +step "s1_vacuum" { VACUUM FREEZE tab_freeze; } + +session "s2" +setup { BEGIN; } +step "s2_key_share" { SELECT id FROM tab_freeze WHERE id = 3 FOR KEY SHARE; } +step "s2_commit" { COMMIT; }