|
|
|
@ -1718,7 +1718,8 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
|
|
|
|
|
* broken.
|
|
|
|
|
*/
|
|
|
|
|
if (TransactionIdIsValid(prev_xmax) &&
|
|
|
|
|
!HeapTupleUpdateXmaxMatchesXmin(prev_xmax, heapTuple->t_data))
|
|
|
|
|
!TransactionIdEquals(prev_xmax,
|
|
|
|
|
HeapTupleHeaderGetXmin(heapTuple->t_data)))
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
@ -1887,7 +1888,7 @@ heap_get_latest_tid(Relation relation,
|
|
|
|
|
* tuple. Check for XMIN match.
|
|
|
|
|
*/
|
|
|
|
|
if (TransactionIdIsValid(priorXmax) &&
|
|
|
|
|
!HeapTupleUpdateXmaxMatchesXmin(priorXmax, tp.t_data))
|
|
|
|
|
!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
|
|
|
|
|
{
|
|
|
|
|
UnlockReleaseBuffer(buffer);
|
|
|
|
|
break;
|
|
|
|
@ -1919,39 +1920,6 @@ heap_get_latest_tid(Relation relation,
|
|
|
|
|
} /* end of loop */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* HeapTupleUpdateXmaxMatchesXmin - verify update chain xmax/xmin lineage
|
|
|
|
|
*
|
|
|
|
|
* Given the new version of a tuple after some update, verify whether the
|
|
|
|
|
* given Xmax (corresponding to the previous version) matches the tuple's
|
|
|
|
|
* Xmin, taking into account that the Xmin might have been frozen after the
|
|
|
|
|
* update.
|
|
|
|
|
*/
|
|
|
|
|
bool
|
|
|
|
|
HeapTupleUpdateXmaxMatchesXmin(TransactionId xmax, HeapTupleHeader htup)
|
|
|
|
|
{
|
|
|
|
|
TransactionId xmin = HeapTupleHeaderGetXmin(htup);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If the xmax of the old tuple is identical to the xmin of the new one,
|
|
|
|
|
* it's a match.
|
|
|
|
|
*/
|
|
|
|
|
if (TransactionIdEquals(xmax, xmin))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* When a tuple is frozen, the original Xmin is lost, but we know it's a
|
|
|
|
|
* committed transaction. So unless the Xmax is InvalidXid, we don't know
|
|
|
|
|
* for certain that there is a match, but there may be one; and we must
|
|
|
|
|
* return true so that a HOT chain that is half-frozen can be walked
|
|
|
|
|
* correctly.
|
|
|
|
|
*/
|
|
|
|
|
if (TransactionIdEquals(xmin, FrozenTransactionId) &&
|
|
|
|
|
TransactionIdIsValid(xmax))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
|
|
|
|
@ -5077,7 +5045,8 @@ l4:
|
|
|
|
|
* the end of the chain, we're done, so return success.
|
|
|
|
|
*/
|
|
|
|
|
if (TransactionIdIsValid(priorXmax) &&
|
|
|
|
|
!HeapTupleUpdateXmaxMatchesXmin(priorXmax, mytup.t_data))
|
|
|
|
|
!TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data),
|
|
|
|
|
priorXmax))
|
|
|
|
|
{
|
|
|
|
|
UnlockReleaseBuffer(buf);
|
|
|
|
|
return HeapTupleMayBeUpdated;
|
|
|
|
@ -5520,26 +5489,14 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
|
|
|
|
|
Assert(TransactionIdIsValid(xid));
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The updating transaction cannot possibly be still running, but
|
|
|
|
|
* verify whether it has committed, and request to set the
|
|
|
|
|
* COMMITTED flag if so. (We normally don't see any tuples in
|
|
|
|
|
* this state, because they are removed by page pruning before we
|
|
|
|
|
* try to freeze the page; but this can happen if the updating
|
|
|
|
|
* transaction commits after the page is pruned but before
|
|
|
|
|
* HeapTupleSatisfiesVacuum).
|
|
|
|
|
* If the xid is older than the cutoff, it has to have aborted,
|
|
|
|
|
* otherwise the tuple would have gotten pruned away.
|
|
|
|
|
*/
|
|
|
|
|
if (TransactionIdPrecedes(xid, cutoff_xid))
|
|
|
|
|
{
|
|
|
|
|
if (TransactionIdDidCommit(xid))
|
|
|
|
|
{
|
|
|
|
|
xid = FrozenTransactionId;
|
|
|
|
|
*flags = FRM_MARK_COMMITTED | FRM_RETURN_IS_XID;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
*flags |= FRM_INVALIDATE_XMAX;
|
|
|
|
|
xid = InvalidTransactionId; /* not strictly necessary */
|
|
|
|
|
}
|
|
|
|
|
Assert(!TransactionIdDidCommit(xid));
|
|
|
|
|
*flags |= FRM_INVALIDATE_XMAX;
|
|
|
|
|
xid = InvalidTransactionId; /* not strictly necessary */
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
@ -5610,17 +5567,18 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* It's an update; should we keep it? If the transaction is known
|
|
|
|
|
* aborted or crashed then it's okay to ignore it, otherwise not.
|
|
|
|
|
* aborted then it's okay to ignore it, otherwise not. However,
|
|
|
|
|
* if the Xid is older than the cutoff_xid, we must remove it.
|
|
|
|
|
* Note that such an old updater cannot possibly be committed,
|
|
|
|
|
* because HeapTupleSatisfiesVacuum would have returned
|
|
|
|
|
* HEAPTUPLE_DEAD and we would not be trying to freeze the tuple.
|
|
|
|
|
*
|
|
|
|
|
* Note the TransactionIdDidAbort() test is just an optimization
|
|
|
|
|
* and not strictly necessary for correctness.
|
|
|
|
|
*
|
|
|
|
|
* As with all tuple visibility routines, it's critical to test
|
|
|
|
|
* TransactionIdIsInProgress before TransactionIdDidCommit,
|
|
|
|
|
* TransactionIdIsInProgress before the transam.c routines,
|
|
|
|
|
* because of race conditions explained in detail in tqual.c.
|
|
|
|
|
*
|
|
|
|
|
* We normally don't see committed updating transactions earlier
|
|
|
|
|
* than the cutoff xid, because they are removed by page pruning
|
|
|
|
|
* before we try to freeze the page; but it can happen if the
|
|
|
|
|
* updating transaction commits after the page is pruned but
|
|
|
|
|
* before HeapTupleSatisfiesVacuum.
|
|
|
|
|
*/
|
|
|
|
|
if (TransactionIdIsCurrentTransactionId(xid) ||
|
|
|
|
|
TransactionIdIsInProgress(xid))
|
|
|
|
@ -5628,33 +5586,46 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
|
|
|
|
|
Assert(!TransactionIdIsValid(update_xid));
|
|
|
|
|
update_xid = xid;
|
|
|
|
|
}
|
|
|
|
|
else if (TransactionIdDidCommit(xid))
|
|
|
|
|
else if (!TransactionIdDidAbort(xid))
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* The transaction committed, so we can tell caller to set
|
|
|
|
|
* HEAP_XMAX_COMMITTED. (We can only do this because we know
|
|
|
|
|
* the transaction is not running.)
|
|
|
|
|
* Test whether to tell caller to set HEAP_XMAX_COMMITTED
|
|
|
|
|
* while we have the Xid still in cache. Note this can only
|
|
|
|
|
* be done if the transaction is known not running.
|
|
|
|
|
*/
|
|
|
|
|
if (TransactionIdDidCommit(xid))
|
|
|
|
|
update_committed = true;
|
|
|
|
|
Assert(!TransactionIdIsValid(update_xid));
|
|
|
|
|
update_committed = true;
|
|
|
|
|
update_xid = xid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Not in progress, not committed -- must be aborted or crashed;
|
|
|
|
|
* we can ignore it.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If we determined that it's an Xid corresponding to an update
|
|
|
|
|
* that must be retained, additionally add it to the list of
|
|
|
|
|
* members of the new Multi, in case we end up using that. (We
|
|
|
|
|
* members of the new Multis, in case we end up using that. (We
|
|
|
|
|
* might still decide to use only an update Xid and not a multi,
|
|
|
|
|
* but it's easier to maintain the list as we walk the old members
|
|
|
|
|
* list.)
|
|
|
|
|
*
|
|
|
|
|
* It is possible to end up with a very old updater Xid that
|
|
|
|
|
* crashed and thus did not mark itself as aborted in pg_clog.
|
|
|
|
|
* That would manifest as a pre-cutoff Xid. Make sure to ignore
|
|
|
|
|
* it.
|
|
|
|
|
*/
|
|
|
|
|
if (TransactionIdIsValid(update_xid))
|
|
|
|
|
newmembers[nnewmembers++] = members[i];
|
|
|
|
|
{
|
|
|
|
|
if (!TransactionIdPrecedes(update_xid, cutoff_xid))
|
|
|
|
|
{
|
|
|
|
|
newmembers[nnewmembers++] = members[i];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* cannot have committed: would be HEAPTUPLE_DEAD */
|
|
|
|
|
Assert(!TransactionIdDidCommit(update_xid));
|
|
|
|
|
update_xid = InvalidTransactionId;
|
|
|
|
|
update_committed = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
@ -5721,10 +5692,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
|
|
|
|
|
*
|
|
|
|
|
* 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). However, note
|
|
|
|
|
* that we don't remove HOT tuples even if they are dead, and it'd be incorrect
|
|
|
|
|
* to freeze them (because that would make them visible), so we mark them as
|
|
|
|
|
* update-committed, and needing further freezing later on.
|
|
|
|
|
* (else we should be removing the tuple, not freezing it).
|
|
|
|
|
*
|
|
|
|
|
* 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
|
|
|
|
@ -5835,18 +5803,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
|
|
|
|
|
else if (TransactionIdIsNormal(xid) &&
|
|
|
|
|
TransactionIdPrecedes(xid, cutoff_xid))
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Must freeze regular XIDs older than the cutoff. We must not freeze
|
|
|
|
|
* a HOT-updated tuple, though; doing so would bring it back to life.
|
|
|
|
|
*/
|
|
|
|
|
if (HeapTupleHeaderIsHotUpdated(tuple))
|
|
|
|
|
{
|
|
|
|
|
frz->t_infomask |= HEAP_XMAX_COMMITTED;
|
|
|
|
|
changed = true;
|
|
|
|
|
/* must not freeze */
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
freeze_xmax = true;
|
|
|
|
|
freeze_xmax = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (freeze_xmax)
|
|
|
|
|