📄 vacuum.c
字号:
* Also note that we don't change the stored fraged_pages list, only * our local variable num_fraged_pages; so the forgotten pages are * still available to be loaded into the free space map later. */ while (num_fraged_pages > 0 && fraged_pages->pagedesc[num_fraged_pages - 1]->blkno >= blkno) { Assert(fraged_pages->pagedesc[num_fraged_pages - 1]->offsets_used == 0); --num_fraged_pages; } /* * Process this page of relation. */ buf = ReadBuffer(onerel, blkno); page = BufferGetPage(buf); vacpage->offsets_free = 0; isempty = PageIsEmpty(page); dowrite = false; /* Is the page in the vacuum_pages list? */ if (blkno == last_vacuum_block) { if (last_vacuum_page->offsets_free > 0) { /* there are dead tuples on this page - clean them */ Assert(!isempty); LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); vacuum_page(onerel, buf, last_vacuum_page); LockBuffer(buf, BUFFER_LOCK_UNLOCK); dowrite = true; } else Assert(isempty); --vacuumed_pages; if (vacuumed_pages > 0) { /* get prev reaped page from vacuum_pages */ last_vacuum_page = vacuum_pages->pagedesc[vacuumed_pages - 1]; last_vacuum_block = last_vacuum_page->blkno; } else { last_vacuum_page = NULL; last_vacuum_block = InvalidBlockNumber; } if (isempty) { ReleaseBuffer(buf); continue; } } else Assert(!isempty); chain_tuple_moved = false; /* no one chain-tuple was moved off * this page, yet */ vacpage->blkno = blkno; maxoff = PageGetMaxOffsetNumber(page); for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum)) { Size tuple_len; HeapTupleData tuple; ItemId itemid = PageGetItemId(page, offnum); if (!ItemIdIsUsed(itemid)) continue; tuple.t_datamcxt = NULL; tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid); tuple_len = tuple.t_len = ItemIdGetLength(itemid); ItemPointerSet(&(tuple.t_self), blkno, offnum); /* --- * VACUUM FULL has an exclusive lock on the relation. So * normally no other transaction can have pending INSERTs or * DELETEs in this relation. A tuple is either: * (a) a tuple in a system catalog, inserted or deleted * by a not yet committed transaction * (b) known dead (XMIN_INVALID, or XMAX_COMMITTED and xmax * is visible to all active transactions) * (c) inserted by a committed xact (XMIN_COMMITTED) * (d) moved by the currently running VACUUM. * (e) deleted (XMAX_COMMITTED) but at least one active * transaction does not see the deleting transaction * In case (a) we wouldn't be in repair_frag() at all. * In case (b) we cannot be here, because scan_heap() has * already marked the item as unused, see continue above. Case * (c) is what normally is to be expected. Case (d) is only * possible, if a whole tuple chain has been moved while * processing this or a higher numbered block. * --- */ if (!(tuple.t_data->t_infomask & HEAP_XMIN_COMMITTED)) { if (tuple.t_data->t_infomask & HEAP_MOVED_IN) elog(ERROR, "HEAP_MOVED_IN was not expected"); if (!(tuple.t_data->t_infomask & HEAP_MOVED_OFF)) elog(ERROR, "HEAP_MOVED_OFF was expected"); /* * MOVED_OFF by another VACUUM would have caused the * visibility check to set XMIN_COMMITTED or XMIN_INVALID. */ if (HeapTupleHeaderGetXvac(tuple.t_data) != myXID) elog(ERROR, "invalid XVAC in tuple header"); /* * If this (chain) tuple is moved by me already then I have to * check is it in vacpage or not - i.e. is it moved while * cleaning this page or some previous one. */ /* Can't we Assert(keep_tuples > 0) here? */ if (keep_tuples == 0) continue; if (chain_tuple_moved) { /* some chains were moved while cleaning this page */ Assert(vacpage->offsets_free > 0); for (i = 0; i < vacpage->offsets_free; i++) { if (vacpage->offsets[i] == offnum) break; } if (i >= vacpage->offsets_free) /* not found */ { vacpage->offsets[vacpage->offsets_free++] = offnum; keep_tuples--; } } else { vacpage->offsets[vacpage->offsets_free++] = offnum; keep_tuples--; } continue; } /* * If this tuple is in a chain of tuples created in updates by * "recent" transactions then we have to move the whole chain of * tuples to other places, so that we can write new t_ctid links * that preserve the chain relationship. * * This test is complicated. Read it as "if tuple is a recently * created updated version, OR if it is an obsoleted version". (In * the second half of the test, we needn't make any check on XMAX * --- it must be recently obsoleted, else scan_heap would have * deemed it removable.) * * NOTE: this test is not 100% accurate: it is possible for a * tuple to be an updated one with recent xmin, and yet not match * any new_tid entry in the vtlinks list. Presumably there was * once a parent tuple with xmax matching the xmin, but it's * possible that that tuple has been removed --- for example, if * it had xmin = xmax and wasn't itself an updated version, then * HeapTupleSatisfiesVacuum would deem it removable as soon as the * xmin xact completes. * * To be on the safe side, we abandon the repair_frag process if * we cannot find the parent tuple in vtlinks. This may be overly * conservative; AFAICS it would be safe to move the chain. */ if (((tuple.t_data->t_infomask & HEAP_UPDATED) && !TransactionIdPrecedes(HeapTupleHeaderGetXmin(tuple.t_data), OldestXmin)) || (!(tuple.t_data->t_infomask & (HEAP_XMAX_INVALID | HEAP_IS_LOCKED)) && !(ItemPointerEquals(&(tuple.t_self), &(tuple.t_data->t_ctid))))) { Buffer Cbuf = buf; bool freeCbuf = false; bool chain_move_failed = false; ItemPointerData Ctid; HeapTupleData tp = tuple; Size tlen = tuple_len; VTupleMove vtmove; int num_vtmove; int free_vtmove; VacPage to_vacpage = NULL; int to_item = 0; int ti; if (dst_buffer != InvalidBuffer) { WriteBuffer(dst_buffer); dst_buffer = InvalidBuffer; } /* Quick exit if we have no vtlinks to search in */ if (vacrelstats->vtlinks == NULL) { elog(DEBUG2, "parent item in update-chain not found --- can't continue repair_frag"); break; /* out of walk-along-page loop */ } /* * 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. */ while (!(tp.t_data->t_infomask & (HEAP_XMAX_INVALID | HEAP_IS_LOCKED)) && !(ItemPointerEquals(&(tp.t_self), &(tp.t_data->t_ctid)))) { ItemPointerData nextTid; TransactionId priorXmax; Buffer nextBuf; Page nextPage; OffsetNumber nextOffnum; ItemId nextItemid; HeapTupleHeader nextTdata; nextTid = tp.t_data->t_ctid; priorXmax = HeapTupleHeaderGetXmax(tp.t_data); /* assume block# is OK (see heap_fetch comments) */ nextBuf = ReadBuffer(onerel, ItemPointerGetBlockNumber(&nextTid)); nextPage = BufferGetPage(nextBuf); /* If bogus or unused slot, assume tp is end of chain */ nextOffnum = ItemPointerGetOffsetNumber(&nextTid); if (nextOffnum < FirstOffsetNumber || nextOffnum > PageGetMaxOffsetNumber(nextPage)) { ReleaseBuffer(nextBuf); break; } nextItemid = PageGetItemId(nextPage, nextOffnum); if (!ItemIdIsUsed(nextItemid)) { ReleaseBuffer(nextBuf); break; } /* if not matching XMIN, assume tp is end of chain */ nextTdata = (HeapTupleHeader) PageGetItem(nextPage, nextItemid); if (!TransactionIdEquals(HeapTupleHeaderGetXmin(nextTdata), priorXmax)) { ReleaseBuffer(nextBuf); break; } /* OK, switch our attention to the next tuple in chain */ tp.t_datamcxt = NULL; tp.t_data = nextTdata; tp.t_self = nextTid; tlen = tp.t_len = ItemIdGetLength(nextItemid); if (freeCbuf) ReleaseBuffer(Cbuf); Cbuf = nextBuf; freeCbuf = true; } /* Set up workspace for planning the chain move */ vtmove = (VTupleMove) palloc(100 * sizeof(VTupleMoveData)); num_vtmove = 0; free_vtmove = 100; /* * Now, walk backwards up the chain (towards older tuples) and * check if all items in chain can be moved. We record all * the moves that need to be made in the vtmove array. */ for (;;) { Buffer Pbuf; Page Ppage; ItemId Pitemid; HeapTupleHeader PTdata; VTupleLinkData vtld, *vtlp; /* Identify a target page to move this tuple to */ if (to_vacpage == NULL || !enough_space(to_vacpage, tlen)) { for (i = 0; i < num_fraged_pages; i++) { if (enough_space(fraged_pages->pagedesc[i], tlen)) break; } if (i == num_fraged_pages) { /* can't move item anywhere */ chain_move_failed = true; break; /* out of check-all-items loop */ } to_item = i; to_vacpage = fraged_pages->pagedesc[to_item]; } to_vacpage->free -= MAXALIGN(tlen); if (to_vacpage->offsets_used >= to_vacpage->offsets_free) to_vacpage->free -= sizeof(ItemIdData); (to_vacpage->offsets_used)++; /* Add an entry to vtmove list */ if (free_vtmove == 0) { free_vtmove = 1000; vtmove = (VTupleMove) repalloc(vtmove, (free_vtmove + num_vtmove) * sizeof(VTupleMoveData)); } vtmove[num_vtmove].tid = tp.t_self; vtmove[num_vtmove].vacpage = to_vacpage; if (to_vacpage->offsets_used == 1) vtmove[num_vtmove].cleanVpd = true; else vtmove[num_vtmove].cleanVpd = false; free_vtmove--; num_vtmove++; /* Done if at beginning of chain */ if (!(tp.t_data->t_infomask & HEAP_UPDATED) || TransactionIdPrecedes(HeapTupleHeaderGetXmin(tp.t_data), OldestXmin)) break; /* out of check-all-items loop */ /* Move to tuple with prior row version */ vtld.new_tid = tp.t_self; vtlp = (VTupleLink) vac_bsearch((void *) &vtld, (void *) (vacrelstats->vtlinks), vacrelstats->num_vtlinks, sizeof(VTupleLinkData), vac_cmp_vtlinks); if (vtlp == NULL) { /* see discussion above */ elog(DEBUG2, "parent item in update-chain not found --- can't continue repair_frag"); chain_move_failed = true; break; /* out of check-all-items loop */ } tp.t_self = vtlp->this_tid; Pbuf = ReadBuffer(onerel, ItemPointerGetBlockNumber(&(tp.t_self))); Ppage = BufferGetPage(Pbuf); Pitemid = PageGetItemId(Ppage, ItemPointerGetOffsetNumber(&(tp.t_self))); /* this can't happen since we saw tuple earlier: */ if (!ItemIdIsUsed(Pitemid)) elog(ERROR, "parent itemid marked as unused"); PTdata = (HeapTupleHeader) PageGetItem(Ppage, Pitemid); /* ctid should not have changed since we saved it */ Assert(ItemPointerEquals(&(vtld.new_tid), &(PTdata->t_ctid))); /* * Read above about cases when !ItemIdIsUsed(nextItemid) * (child item is removed)... Due to the fact that at the * moment we don't remove unuseful part of update-chain, * it's possible to get non-matching parent row here. Like * as in the case which caused this problem, we stop * shrinking here. I could try to find real parent row but * want not to do it because of real solution will be * implemented anyway, later, and we are too close to 6.5 * release. - vadim 06/11/99 */ if ((PTdata->t_infomask & HEAP_XMAX_IS_MULTI) || !(TransactionIdEquals(HeapTupleHeaderGetXmax(PTdata), HeapTupleHeaderGetXmin(tp.t_data)))) { ReleaseBuffer(Pbuf); elog(DEBUG2, "too old parent tuple found --- can't continue repair_frag"); chain_move_failed = true; break; /* out of check-all-items loop */ } tp.t_datamcxt = NULL; tp.t_data = PTdata; tlen = tp.t_len = ItemIdGetLength(Pitemid); if (freeCbuf) ReleaseBuffer(Cbuf); Cbuf = Pbuf; freeCbuf = true; } /* end of check-all-items loop */ if (freeCbuf) ReleaseBuffer(Cbuf); freeCbuf = false; if (chain_move_failed) { /* * Undo changes to offsets_used state. We don't bother * cleaning up the amount-free state, since we're not * going to do any further tuple motion. */ for (i = 0; i < num_vtmove; i++) { Assert(vtmove[i].vacpage->offsets_used > 0); (vtmove[i].vacpage->offsets_used)--; } pfree(vtmove); break; /* out of walk-along-page loop */ } /* * Okay, move the whole tuple chain in reverse order. * * Ctid tracks the new location of the previously-moved tuple. */ ItemPointerSetInvalid(&Ctid); for (ti = 0; ti < num_vtmove; ti++) { VacPage destvacpage = vtmove[ti].vacpage; Page Cpage; ItemId Citemid; /* Get page to move from */ tuple.t_self = vtmove[ti].tid; Cbuf = ReadBuffer(onerel, ItemPointerGetBlockNumber(&(tuple.t_
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -