*** a/src/backend/access/heap/heapam.c --- b/src/backend/access/heap/heapam.c *************** *** 1529,1535 **** heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, OffsetNumber offnum; bool at_chain_start; bool valid; - bool match_found; if (all_dead) *all_dead = true; --- 1529,1534 ---- *************** *** 1539,1545 **** heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer)); offnum = ItemPointerGetOffsetNumber(tid); at_chain_start = true; - match_found = false; /* Scan through possible multiple members of HOT-chain */ for (;;) --- 1538,1543 ---- *************** *** 1597,1606 **** heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, PredicateLockTuple(relation, &heapTuple); if (all_dead) *all_dead = false; ! if (IsolationIsSerializable()) ! match_found = true; ! else ! return true; } /* --- 1595,1601 ---- PredicateLockTuple(relation, &heapTuple); if (all_dead) *all_dead = false; ! return true; } /* *************** *** 1629,1635 **** heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, break; /* end of chain */ } ! return match_found; } /* --- 1624,1630 ---- break; /* end of chain */ } ! return false; } /* *************** *** 2855,2866 **** l2: END_CRIT_SECTION(); - /* - * Any existing SIREAD locks on the old tuple must be linked to the new - * tuple for conflict detection purposes. - */ - PredicateLockTupleRowVersionLink(relation, &oldtup, heaptup); - if (newbuf != buffer) LockBuffer(newbuf, BUFFER_LOCK_UNLOCK); LockBuffer(buffer, BUFFER_LOCK_UNLOCK); --- 2850,2855 ---- *** a/src/backend/access/index/indexam.c --- b/src/backend/access/index/indexam.c *************** *** 612,619 **** index_getnext(IndexScanDesc scan, ScanDirection direction) * any more members. Otherwise, check for continuation of the * HOT-chain, and set state for next time. */ ! if (IsMVCCSnapshot(scan->xs_snapshot) ! && !IsolationIsSerializable()) scan->xs_next_hot = InvalidOffsetNumber; else if (HeapTupleIsHotUpdated(heapTuple)) { --- 612,618 ---- * any more members. Otherwise, check for continuation of the * HOT-chain, and set state for next time. */ ! if (IsMVCCSnapshot(scan->xs_snapshot)) scan->xs_next_hot = InvalidOffsetNumber; else if (HeapTupleIsHotUpdated(heapTuple)) { *** a/src/backend/storage/lmgr/predicate.c --- b/src/backend/storage/lmgr/predicate.c *************** *** 155,163 **** * BlockNumber newblkno); * PredicateLockPageCombine(Relation relation, BlockNumber oldblkno, * BlockNumber newblkno); - * PredicateLockTupleRowVersionLink(const Relation relation, - * const HeapTuple oldTuple, - * const HeapTuple newTuple) * ReleasePredicateLocks(bool isCommit) * * conflict detection (may also trigger rollback) --- 155,160 ---- *************** *** 2252,2341 **** PredicateLockTuple(const Relation relation, const HeapTuple tuple) PredicateLockAcquire(&tag); } - /* - * If the old tuple has any predicate locks, copy them to the new target. - * - * This is called at an UPDATE, where any predicate locks held on the old - * tuple need to be copied to the new tuple, because logically they both - * represent the same row. A lock taken before the update must conflict - * with anyone locking the same row after the update. - */ - void - PredicateLockTupleRowVersionLink(const Relation relation, - const HeapTuple oldTuple, - const HeapTuple newTuple) - { - PREDICATELOCKTARGETTAG oldtupletag; - PREDICATELOCKTARGETTAG oldpagetag; - PREDICATELOCKTARGETTAG newtupletag; - BlockNumber oldblk, - newblk; - OffsetNumber oldoff, - newoff; - TransactionId oldxmin, - newxmin; - - /* - * Bail out quickly if there are no serializable transactions - * running. - * - * It's safe to do this check without taking any additional - * locks. Even if a serializable transaction starts concurrently, - * we know it can't take any SIREAD locks on the modified tuple - * because the caller is holding the associated buffer page lock. - * Memory reordering isn't an issue; the memory barrier in the - * LWLock acquisition guarantees that this read occurs while the - * buffer page lock is held. - */ - if (!TransactionIdIsValid(PredXact->SxactGlobalXmin)) - return; - - oldblk = ItemPointerGetBlockNumber(&(oldTuple->t_self)); - oldoff = ItemPointerGetOffsetNumber(&(oldTuple->t_self)); - oldxmin = HeapTupleHeaderGetXmin(oldTuple->t_data); - - newblk = ItemPointerGetBlockNumber(&(newTuple->t_self)); - newoff = ItemPointerGetOffsetNumber(&(newTuple->t_self)); - newxmin = HeapTupleHeaderGetXmin(newTuple->t_data); - - SET_PREDICATELOCKTARGETTAG_TUPLE(oldtupletag, - relation->rd_node.dbNode, - relation->rd_id, - oldblk, - oldoff, - oldxmin); - - SET_PREDICATELOCKTARGETTAG_PAGE(oldpagetag, - relation->rd_node.dbNode, - relation->rd_id, - oldblk); - - SET_PREDICATELOCKTARGETTAG_TUPLE(newtupletag, - relation->rd_node.dbNode, - relation->rd_id, - newblk, - newoff, - newxmin); - - /* - * A page-level lock on the page containing the old tuple counts too. - * Anyone holding a lock on the page is logically holding a lock on the - * old tuple, so we need to acquire a lock on his behalf on the new tuple - * too. However, if the new tuple is on the same page as the old one, the - * old page-level lock already covers the new tuple. - * - * A relation-level lock always covers both tuple versions, so we don't - * need to worry about those here. - */ - LWLockAcquire(SerializablePredicateLockListLock, LW_EXCLUSIVE); - - TransferPredicateLocksToNewTarget(oldtupletag, newtupletag, false); - if (newblk != oldblk) - TransferPredicateLocksToNewTarget(oldpagetag, newtupletag, false); - - LWLockRelease(SerializablePredicateLockListLock); - } - /* * DeleteLockTarget --- 2249,2254 ---- *************** *** 2650,2658 **** PredicateLockPageSplit(const Relation relation, const BlockNumber oldblkno, /* * Bail out quickly if there are no serializable transactions ! * running. As with PredicateLockTupleRowVersionLink, it's safe to ! * check this without taking locks because the caller is holding ! * the buffer page lock. */ if (!TransactionIdIsValid(PredXact->SxactGlobalXmin)) return; --- 2563,2577 ---- /* * Bail out quickly if there are no serializable transactions ! * running. ! * ! * It's safe to do this check without taking any additional ! * locks. Even if a serializable transaction starts concurrently, ! * we know it can't take any SIREAD locks on the page being split ! * because the caller is holding the associated buffer page lock. ! * Memory reordering isn't an issue; the memory barrier in the ! * LWLock acquisition guarantees that this read occurs while the ! * buffer page lock is held. */ if (!TransactionIdIsValid(PredXact->SxactGlobalXmin)) return; *************** *** 3955,3963 **** OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *reader, * before the reader acquired its snapshot. (That is, the reader must not * have been concurrent with the out-conflict transaction.) */ ! if (!failure && !SxactIsCommitted(writer)) { ! if (SxactHasSummaryConflictOut(reader)) { failure = true; conflict = NULL; --- 3874,3882 ---- * before the reader acquired its snapshot. (That is, the reader must not * have been concurrent with the out-conflict transaction.) */ ! if (!failure) { ! if (SxactHasSummaryConflictOut(writer)) { failure = true; conflict = NULL; *************** *** 3969,3979 **** OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *reader, offsetof(RWConflictData, outLink)); while (conflict) { ! if ((reader == conflict->sxactIn && SxactIsCommitted(reader)) ! || (SxactIsCommitted(conflict->sxactIn) ! && !SxactIsCommitted(reader) ! && (!SxactIsReadOnly(reader) ! || conflict->sxactIn->commitSeqNo <= reader->SeqNo.lastCommitBeforeSnapshot))) { failure = true; break; --- 3888,3900 ---- offsetof(RWConflictData, outLink)); while (conflict) { ! if (SxactIsCommitted(conflict->sxactIn) ! && (!SxactIsCommitted(reader) ! || conflict->sxactIn->commitSeqNo <= reader->commitSeqNo) ! && (!SxactIsCommitted(writer) ! || conflict->sxactIn->commitSeqNo <= writer->commitSeqNo) ! && (!SxactIsReadOnly(reader) ! || conflict->sxactIn->commitSeqNo <= reader->SeqNo.lastCommitBeforeSnapshot)) { failure = true; break; *** a/src/include/storage/predicate.h --- b/src/include/storage/predicate.h *************** *** 47,53 **** extern void RegisterPredicateLockingXid(const TransactionId xid); extern void PredicateLockRelation(const Relation relation); extern void PredicateLockPage(const Relation relation, const BlockNumber blkno); extern void PredicateLockTuple(const Relation relation, const HeapTuple tuple); - extern void PredicateLockTupleRowVersionLink(const Relation relation, const HeapTuple oldTuple, const HeapTuple newTuple); extern void PredicateLockPageSplit(const Relation relation, const BlockNumber oldblkno, const BlockNumber newblkno); extern void PredicateLockPageCombine(const Relation relation, const BlockNumber oldblkno, const BlockNumber newblkno); extern void ReleasePredicateLocks(const bool isCommit); --- 47,52 ---- *** a/src/test/isolation/expected/multiple-row-versions.out --- b/src/test/isolation/expected/multiple-row-versions.out *************** *** 19,24 **** id txt 1 step c4: COMMIT; step c3: COMMIT; - ERROR: could not serialize access due to read/write dependencies among transactions step wz1: UPDATE t SET txt = 'a' WHERE id = 1; step c1: COMMIT; --- 19,24 ---- 1 step c4: COMMIT; step c3: COMMIT; step wz1: UPDATE t SET txt = 'a' WHERE id = 1; + ERROR: could not serialize access due to read/write dependencies among transactions step c1: COMMIT; *** a/src/test/isolation/specs/multiple-row-versions.spec --- b/src/test/isolation/specs/multiple-row-versions.spec *************** *** 1,8 **** # Multiple Row Versions test # ! # This test is designed to ensure that predicate locks taken on one version ! # of a row are detected as conflicts when a later version of the row is ! # updated or deleted by a transaction concurrent to the reader. # # Due to long permutation setup time, we are only testing one specific # permutation, which should get a serialization error. --- 1,7 ---- # Multiple Row Versions test # ! # This test is designed to cover some code paths which only occur with ! # four or more transactions interacting with particular timings. # # Due to long permutation setup time, we are only testing one specific # permutation, which should get a serialization error.