mirror of
https://github.com/postgres/postgres.git
synced 2025-05-17 06:41:24 +03:00
Fix aboriginal mistake in lazy VACUUM's code for truncating away
no-longer-needed pages at the end of a table. We thought we could throw away pages containing HEAPTUPLE_DEAD tuples; but this is not so, because such tuples very likely have index entries pointing at them, and we wouldn't have removed the index entries. The problem only emerges in a somewhat unlikely race condition: the dead tuples have to have been inserted by a transaction that later aborted, and this has to have happened between VACUUM's initial scan of the page and then rechecking it for empty in count_nondeletable_pages. But that timespan will include an index-cleaning pass, so it's not all that hard to hit. This seems to explain a couple of previously unsolved bug reports.
This commit is contained in:
parent
2f2f9b9bae
commit
11a8925afd
@ -31,7 +31,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/commands/vacuumlazy.c,v 1.32.2.1 2005/05/07 21:33:21 tgl Exp $
|
* $Header: /cvsroot/pgsql/src/backend/commands/vacuumlazy.c,v 1.32.2.2 2007/09/16 02:38:25 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -798,7 +798,7 @@ lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Rescan end pages to verify that they are (still) empty of needed tuples.
|
* Rescan end pages to verify that they are (still) empty of tuples.
|
||||||
*
|
*
|
||||||
* Returns number of nondeletable pages (last nonempty page + 1).
|
* Returns number of nondeletable pages (last nonempty page + 1).
|
||||||
*/
|
*/
|
||||||
@ -806,7 +806,6 @@ static BlockNumber
|
|||||||
count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
|
count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
|
||||||
{
|
{
|
||||||
BlockNumber blkno;
|
BlockNumber blkno;
|
||||||
HeapTupleData tuple;
|
|
||||||
|
|
||||||
/* Strange coding of loop control is needed because blkno is unsigned */
|
/* Strange coding of loop control is needed because blkno is unsigned */
|
||||||
blkno = vacrelstats->rel_pages;
|
blkno = vacrelstats->rel_pages;
|
||||||
@ -816,9 +815,7 @@ count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
|
|||||||
Page page;
|
Page page;
|
||||||
OffsetNumber offnum,
|
OffsetNumber offnum,
|
||||||
maxoff;
|
maxoff;
|
||||||
bool pgchanged,
|
bool hastup;
|
||||||
tupgone,
|
|
||||||
hastup;
|
|
||||||
|
|
||||||
CHECK_FOR_INTERRUPTS();
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
|
||||||
@ -833,13 +830,12 @@ count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
|
|||||||
|
|
||||||
if (PageIsNew(page) || PageIsEmpty(page))
|
if (PageIsNew(page) || PageIsEmpty(page))
|
||||||
{
|
{
|
||||||
/* PageIsNew robably shouldn't happen... */
|
/* PageIsNew probably shouldn't happen... */
|
||||||
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
|
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
|
||||||
ReleaseBuffer(buf);
|
ReleaseBuffer(buf);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
pgchanged = false;
|
|
||||||
hastup = false;
|
hastup = false;
|
||||||
maxoff = PageGetMaxOffsetNumber(page);
|
maxoff = PageGetMaxOffsetNumber(page);
|
||||||
for (offnum = FirstOffsetNumber;
|
for (offnum = FirstOffsetNumber;
|
||||||
@ -847,52 +843,16 @@ count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
|
|||||||
offnum = OffsetNumberNext(offnum))
|
offnum = OffsetNumberNext(offnum))
|
||||||
{
|
{
|
||||||
ItemId itemid;
|
ItemId itemid;
|
||||||
uint16 sv_infomask;
|
|
||||||
|
|
||||||
itemid = PageGetItemId(page, offnum);
|
itemid = PageGetItemId(page, offnum);
|
||||||
|
|
||||||
if (!ItemIdIsUsed(itemid))
|
/*
|
||||||
continue;
|
* Note: any non-unused item should be taken as a reason to keep
|
||||||
|
* this page. We formerly thought that DEAD tuples could be
|
||||||
tuple.t_datamcxt = NULL;
|
* thrown away, but that's not so, because we'd not have cleaned
|
||||||
tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
|
* out their index entries.
|
||||||
tuple.t_len = ItemIdGetLength(itemid);
|
*/
|
||||||
ItemPointerSet(&(tuple.t_self), blkno, offnum);
|
if (ItemIdIsUsed(itemid))
|
||||||
|
|
||||||
tupgone = false;
|
|
||||||
sv_infomask = tuple.t_data->t_infomask;
|
|
||||||
|
|
||||||
switch (HeapTupleSatisfiesVacuum(tuple.t_data, OldestXmin))
|
|
||||||
{
|
|
||||||
case HEAPTUPLE_DEAD:
|
|
||||||
tupgone = true; /* we can delete the tuple */
|
|
||||||
break;
|
|
||||||
case HEAPTUPLE_LIVE:
|
|
||||||
/* Shouldn't be necessary to re-freeze anything */
|
|
||||||
break;
|
|
||||||
case HEAPTUPLE_RECENTLY_DEAD:
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If tuple is recently deleted then we must not
|
|
||||||
* remove it from relation.
|
|
||||||
*/
|
|
||||||
break;
|
|
||||||
case HEAPTUPLE_INSERT_IN_PROGRESS:
|
|
||||||
/* This is an expected case during concurrent vacuum */
|
|
||||||
break;
|
|
||||||
case HEAPTUPLE_DELETE_IN_PROGRESS:
|
|
||||||
/* This is an expected case during concurrent vacuum */
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check for hint-bit update by HeapTupleSatisfiesVacuum */
|
|
||||||
if (sv_infomask != tuple.t_data->t_infomask)
|
|
||||||
pgchanged = true;
|
|
||||||
|
|
||||||
if (!tupgone)
|
|
||||||
{
|
{
|
||||||
hastup = true;
|
hastup = true;
|
||||||
break; /* can stop scanning */
|
break; /* can stop scanning */
|
||||||
@ -900,11 +860,7 @@ count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
|
|||||||
} /* scan along page */
|
} /* scan along page */
|
||||||
|
|
||||||
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
|
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
|
||||||
|
ReleaseBuffer(buf);
|
||||||
if (pgchanged)
|
|
||||||
WriteBuffer(buf);
|
|
||||||
else
|
|
||||||
ReleaseBuffer(buf);
|
|
||||||
|
|
||||||
/* Done scanning if we found a tuple here */
|
/* Done scanning if we found a tuple here */
|
||||||
if (hastup)
|
if (hastup)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user