*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.263.2.2 2005/08/25 22:07:16 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.263.2.3 2007/03/14 18:49:26 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* we cannot find the parent tuple in vtlinks. This may be
* overly conservative; AFAICS it would be safe to move the
* chain.
+ *
+ * Also, because we distinguish DEAD and RECENTLY_DEAD tuples
+ * using OldestXmin, which is a rather coarse test, it is quite
+ * possible to have an update chain in which a tuple we think is
+ * RECENTLY_DEAD links forward to one that is definitely DEAD.
+ * In such a case the RECENTLY_DEAD tuple must actually be dead,
+ * but it seems too complicated to try to make VACUUM remove it.
+ * We treat each contiguous set of RECENTLY_DEAD tuples as a
+ * separately movable chain, ignoring any intervening DEAD ones.
*/
if (((tuple.t_data->t_infomask & HEAP_UPDATED) &&
!TransactionIdPrecedes(HeapTupleHeaderGetXmin(tuple.t_data),
Buffer Cbuf = buf;
bool freeCbuf = false;
bool chain_move_failed = false;
+ bool moved_target = false;
ItemPointerData Ctid;
HeapTupleData tp = tuple;
Size tlen = tuple_len;
* If this tuple is in the begin/middle of the chain then
* we have to move to the end of chain. As with any
* t_ctid chase, we have to verify that each new tuple
- * is really the descendant of the tuple we came from.
+ * is really the descendant of the tuple we came from;
+ * however, here we need even more than the normal amount of
+ * paranoia. If t_ctid links forward to a tuple determined
+ * to be DEAD, then depending on where that tuple is, it
+ * might already have been removed, and perhaps even replaced
+ * by a MOVED_IN tuple. We don't want to include any DEAD
+ * tuples in the chain, so we have to recheck
+ * HeapTupleSatisfiesVacuum.
*/
while (!(tp.t_data->t_infomask & (HEAP_XMAX_INVALID |
HEAP_MARKED_FOR_UPDATE)) &&
OffsetNumber nextOffnum;
ItemId nextItemid;
HeapTupleHeader nextTdata;
+ uint16 sv_infomask;
+ HTSV_Result nextTstatus;
nextTid = tp.t_data->t_ctid;
priorXmax = HeapTupleHeaderGetXmax(tp.t_data);
ReleaseBuffer(nextBuf);
break;
}
+ /* must check for DEAD or MOVED_IN tuple, too */
+ sv_infomask = nextTdata->t_infomask;
+ nextTstatus = HeapTupleSatisfiesVacuum(nextTdata,
+ OldestXmin);
+ /* not sure hint-bit change can happen, but be careful */
+ if (sv_infomask != nextTdata->t_infomask)
+ SetBufferCommitInfoNeedsSave(nextBuf);
+ if (nextTstatus == HEAPTUPLE_DEAD ||
+ nextTstatus == HEAPTUPLE_INSERT_IN_PROGRESS)
+ {
+ ReleaseBuffer(nextBuf);
+ break;
+ }
+ /* if it's MOVED_OFF we shoulda moved this one with it */
+ if (nextTstatus == HEAPTUPLE_DELETE_IN_PROGRESS)
+ elog(ERROR, "updated tuple is already HEAP_MOVED_OFF");
/* OK, switch our attention to the next tuple in chain */
tp.t_datamcxt = NULL;
tp.t_data = nextTdata;
free_vtmove--;
num_vtmove++;
+ /* Remember if we reached the original target tuple */
+ if (ItemPointerGetBlockNumber(&tp.t_self) == blkno &&
+ ItemPointerGetOffsetNumber(&tp.t_self) == offnum)
+ moved_target = true;
+
/* At beginning of chain? */
if (!(tp.t_data->t_infomask & HEAP_UPDATED) ||
TransactionIdPrecedes(HeapTupleHeaderGetXmin(tp.t_data),
ReleaseBuffer(Cbuf);
freeCbuf = false;
+ /* Double-check that we will move the current target tuple */
+ if (!moved_target && !chain_move_failed)
+ {
+ elog(DEBUG2, "failed to chain back to target --- cannot continue repair_frag");
+ chain_move_failed = true;
+ }
+
if (chain_move_failed)
{
/*