From 26f1202ca318459753a39b2ced5cb6ea9cd8ab8d Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 26 Aug 2005 20:07:17 +0000 Subject: Back-patch fixes for problems with VACUUM destroying t_ctid chains too soon, and with insufficient paranoia in code that follows t_ctid links. This patch covers the 7.3 branch. --- src/backend/executor/execMain.c | 137 ++++++++++++++++++++++++++++++---------- 1 file changed, 103 insertions(+), 34 deletions(-) (limited to 'src/backend/executor/execMain.c') diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 622cfa93713..b6684579f1b 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -27,7 +27,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.180.2.2 2003/03/27 14:33:21 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.180.2.3 2005/08/26 20:07:17 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1012,8 +1012,10 @@ lnext: ; foreach(l, estate->es_rowMark) { execRowMark *erm = lfirst(l); - Buffer buffer; HeapTupleData tuple; + Buffer buffer; + ItemPointerData update_ctid; + TransactionId update_xmax; TupleTableSlot *newSlot; int test; @@ -1032,6 +1034,7 @@ lnext: ; tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); test = heap_mark4update(erm->relation, &tuple, &buffer, + &update_ctid, &update_xmax, estate->es_snapshot->curcid); ReleaseBuffer(buffer); switch (test) @@ -1046,11 +1049,15 @@ lnext: ; case HeapTupleUpdated: if (XactIsoLevel == XACT_SERIALIZABLE) elog(ERROR, "Can't serialize access due to concurrent update"); - if (!(ItemPointerEquals(&(tuple.t_self), - (ItemPointer) DatumGetPointer(datum)))) + if (!ItemPointerEquals(&update_ctid, + &tuple.t_self)) { - newSlot = EvalPlanQual(estate, erm->rti, &(tuple.t_self)); - if (!(TupIsNull(newSlot))) + /* updated, so look at updated version */ + newSlot = EvalPlanQual(estate, + erm->rti, + &update_ctid, + update_xmax); + if (!TupIsNull(newSlot)) { slot = newSlot; estate->es_useEvalPlan = true; @@ -1280,8 +1287,9 @@ ExecDelete(TupleTableSlot *slot, { ResultRelInfo *resultRelInfo; Relation resultRelationDesc; - ItemPointerData ctid; int result; + ItemPointerData update_ctid; + TransactionId update_xmax; /* * get information on the (current) result relation @@ -1307,7 +1315,7 @@ ExecDelete(TupleTableSlot *slot, */ ldelete:; result = heap_delete(resultRelationDesc, tupleid, - &ctid, + &update_ctid, &update_xmax, estate->es_snapshot->curcid); switch (result) { @@ -1321,14 +1329,17 @@ ldelete:; case HeapTupleUpdated: if (XactIsoLevel == XACT_SERIALIZABLE) elog(ERROR, "Can't serialize access due to concurrent update"); - else if (!(ItemPointerEquals(tupleid, &ctid))) + else if (!ItemPointerEquals(tupleid, &update_ctid)) { - TupleTableSlot *epqslot = EvalPlanQual(estate, - resultRelInfo->ri_RangeTableIndex, &ctid); + TupleTableSlot *epqslot; + epqslot = EvalPlanQual(estate, + resultRelInfo->ri_RangeTableIndex, + &update_ctid, + update_xmax); if (!TupIsNull(epqslot)) { - *tupleid = ctid; + *tupleid = update_ctid; goto ldelete; } } @@ -1376,8 +1387,9 @@ ExecUpdate(TupleTableSlot *slot, HeapTuple tuple; ResultRelInfo *resultRelInfo; Relation resultRelationDesc; - ItemPointerData ctid; int result; + ItemPointerData update_ctid; + TransactionId update_xmax; int numIndices; /* @@ -1443,7 +1455,7 @@ lreplace:; * replace the heap tuple */ result = heap_update(resultRelationDesc, tupleid, tuple, - &ctid, + &update_ctid, &update_xmax, estate->es_snapshot->curcid); switch (result) { @@ -1457,14 +1469,17 @@ lreplace:; case HeapTupleUpdated: if (XactIsoLevel == XACT_SERIALIZABLE) elog(ERROR, "Can't serialize access due to concurrent update"); - else if (!(ItemPointerEquals(tupleid, &ctid))) + else if (!(ItemPointerEquals(tupleid, &update_ctid))) { - TupleTableSlot *epqslot = EvalPlanQual(estate, - resultRelInfo->ri_RangeTableIndex, &ctid); + TupleTableSlot *epqslot; + epqslot = EvalPlanQual(estate, + resultRelInfo->ri_RangeTableIndex, + &update_ctid, + update_xmax); if (!TupIsNull(epqslot)) { - *tupleid = ctid; + *tupleid = update_ctid; tuple = ExecRemoveJunk(estate->es_junkFilter, epqslot); slot = ExecStoreTuple(tuple, estate->es_junkFilter->jf_resultSlot, @@ -1606,9 +1621,21 @@ ExecConstraints(const char *caller, ResultRelInfo *resultRelInfo, * under READ COMMITTED rules. * * See backend/executor/README for some info about how this works. + * + * estate - executor state data + * rti - rangetable index of table containing tuple + * *tid - t_ctid from the outdated tuple (ie, next updated version) + * priorXmax - t_xmax from the outdated tuple + * + * *tid is also an output parameter: it's modified to hold the TID of the + * latest version of the tuple (note this may be changed even on failure) + * + * Returns a slot containing the new candidate update/delete tuple, or + * NULL if we determine we shouldn't process the row. */ TupleTableSlot * -EvalPlanQual(EState *estate, Index rti, ItemPointer tid) +EvalPlanQual(EState *estate, Index rti, + ItemPointer tid, TransactionId priorXmax) { evalPlanQual *epq; EState *epqstate; @@ -1653,10 +1680,24 @@ EvalPlanQual(EState *estate, Index rti, ItemPointer tid) { Buffer buffer; - if (heap_fetch(relation, SnapshotDirty, &tuple, &buffer, false, NULL)) + if (heap_fetch(relation, SnapshotDirty, &tuple, &buffer, true, NULL)) { - TransactionId xwait = SnapshotDirty->xmax; + /* + * If xmin isn't what we're expecting, the slot must have been + * recycled and reused for an unrelated tuple. This implies + * that the latest version of the row was deleted, so we need + * do nothing. (Should be safe to examine xmin without getting + * buffer's content lock, since xmin never changes in an existing + * tuple.) + */ + if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data), + priorXmax)) + { + ReleaseBuffer(buffer); + return NULL; + } + /* otherwise xmin should not be dirty... */ if (TransactionIdIsValid(SnapshotDirty->xmin)) elog(ERROR, "EvalPlanQual: t_xmin is uncommitted ?!"); @@ -1664,11 +1705,11 @@ EvalPlanQual(EState *estate, Index rti, ItemPointer tid) * If tuple is being updated by other transaction then we have * to wait for its commit/abort. */ - if (TransactionIdIsValid(xwait)) + if (TransactionIdIsValid(SnapshotDirty->xmax)) { ReleaseBuffer(buffer); - XactLockTableWait(xwait); - continue; + XactLockTableWait(SnapshotDirty->xmax); + continue; /* loop back to repeat heap_fetch */ } /* @@ -1680,22 +1721,50 @@ EvalPlanQual(EState *estate, Index rti, ItemPointer tid) } /* - * Oops! Invalid tuple. Have to check is it updated or deleted. - * Note that it's possible to get invalid SnapshotDirty->tid if - * tuple updated by this transaction. Have we to check this ? + * If the referenced slot was actually empty, the latest version + * of the row must have been deleted, so we need do nothing. + */ + if (tuple.t_data == NULL) + { + ReleaseBuffer(buffer); + return NULL; + } + + /* + * As above, if xmin isn't what we're expecting, do nothing. */ - if (ItemPointerIsValid(&(SnapshotDirty->tid)) && - !(ItemPointerEquals(&(tuple.t_self), &(SnapshotDirty->tid)))) + if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data), + priorXmax)) { - /* updated, so look at the updated copy */ - tuple.t_self = SnapshotDirty->tid; - continue; + ReleaseBuffer(buffer); + return NULL; } /* - * Deleted or updated by this transaction; forget it. + * If we get here, the tuple was found but failed SnapshotDirty. + * Assuming the xmin is either a committed xact or our own xact + * (as it certainly should be if we're trying to modify the tuple), + * this must mean that the row was updated or deleted by either + * a committed xact or our own xact. If it was deleted, we can + * ignore it; if it was updated then chain up to the next version + * and repeat the whole test. + * + * As above, it should be safe to examine xmax and t_ctid without + * the buffer content lock, because they can't be changing. */ - return NULL; + if (ItemPointerEquals(&tuple.t_self, &tuple.t_data->t_ctid)) + { + /* deleted, so forget about it */ + ReleaseBuffer(buffer); + return NULL; + } + + /* updated, so look at the updated row */ + tuple.t_self = tuple.t_data->t_ctid; + /* updated row should have xmin matching this xmax */ + priorXmax = HeapTupleHeaderGetXmax(tuple.t_data); + ReleaseBuffer(buffer); + /* loop back to fetch next in chain */ } /* -- cgit v1.2.3