forked from huawei/openGauss-server
1765 lines
70 KiB
C++
Executable File
1765 lines
70 KiB
C++
Executable File
/* -------------------------------------------------------------------------
|
|
*
|
|
* tqual.c
|
|
* POSTGRES "time qualification" code, ie, tuple visibility rules.
|
|
*
|
|
* NOTE: all the HeapTupleSatisfies routines will update the tuple's
|
|
* "hint" status bits if we see that the inserting or deleting transaction
|
|
* has now committed or aborted (and it is safe to set the hint bits).
|
|
* If the hint bits are changed, MarkBufferDirtyHint is called on
|
|
* the passed-in buffer. The caller must hold not only a pin, but at least
|
|
* shared buffer content lock on the buffer containing the tuple.
|
|
*
|
|
* NOTE: must check TransactionIdIsInProgress (which looks in PGXACT array)
|
|
* before TransactionIdDidCommit/TransactionIdDidAbort (which look in
|
|
* pg_clog). Otherwise we have a race condition: we might decide that a
|
|
* just-committed transaction crashed, because none of the tests succeed.
|
|
* xact.c is careful to record commit/abort in pg_clog before it unsets
|
|
* MyPgXact->xid in PGXACT array. That fixes that problem, but it also
|
|
* means there is a window where TransactionIdIsInProgress and
|
|
* TransactionIdDidCommit will both return true. If we check only
|
|
* TransactionIdDidCommit, we could consider a tuple committed when a
|
|
* later GetSnapshotData call will still think the originating transaction
|
|
* is in progress, which leads to application-level inconsistency. The
|
|
* upshot is that we gotta check TransactionIdIsInProgress first in all
|
|
* code paths, except for a few cases where we are looking at
|
|
* subtransactions of our own main transaction and so there can't be any
|
|
* race condition.
|
|
*
|
|
* Summary of visibility functions:
|
|
*
|
|
* HeapTupleSatisfiesMVCC()
|
|
* visible to supplied snapshot, excludes current command
|
|
* HeapTupleSatisfiesNow()
|
|
* visible to instant snapshot, excludes current command
|
|
* HeapTupleSatisfiesUpdate()
|
|
* like HeapTupleSatisfiesNow(), but with user-supplied command
|
|
* counter and more complex result
|
|
* HeapTupleSatisfiesSelf()
|
|
* visible to instant snapshot and current command
|
|
* HeapTupleSatisfiesDirty()
|
|
* like HeapTupleSatisfiesSelf(), but includes open transactions
|
|
* HeapTupleSatisfiesVacuum()
|
|
* visible to any running transaction, used by VACUUM
|
|
* HeapTupleSatisfiesToast()
|
|
* visible unless part of interrupted vacuum, used for TOAST
|
|
* HeapTupleSatisfiesAny()
|
|
* all tuples are visible
|
|
*
|
|
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/utils/time/tqual.c
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
#include "knl/knl_variable.h"
|
|
|
|
#include "access/clog.h"
|
|
#include "access/csnlog.h"
|
|
#include "access/htup.h"
|
|
#include "access/multixact.h"
|
|
#include "access/subtrans.h"
|
|
#include "access/transam.h"
|
|
#include "access/twophase.h"
|
|
#include "access/xact.h"
|
|
#include "access/xlog.h"
|
|
#include "storage/bufmgr.h"
|
|
#include "storage/procarray.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/combocid.h"
|
|
#include "utils/snapmgr.h"
|
|
#include "utils/tqual.h"
|
|
#include "commands/vacuum.h"
|
|
|
|
/* Static variables representing various special snapshot semantics */
|
|
THR_LOCAL SnapshotData SnapshotNowData = {HeapTupleSatisfiesNow};
|
|
THR_LOCAL SnapshotData SnapshotSelfData = {HeapTupleSatisfiesSelf};
|
|
THR_LOCAL SnapshotData SnapshotAnyData = {HeapTupleSatisfiesAny};
|
|
THR_LOCAL SnapshotData SnapshotToastData = {HeapTupleSatisfiesToast};
|
|
|
|
#ifdef USE_ASSERT_CHECKING
|
|
#define LatestTransactionStatusError(xid, snapshot, action) PrintCurrentSnapshotInfo(PANIC, xid, snapshot, action)
|
|
#else
|
|
#define LatestTransactionStatusError(xid, snapshot, action) PrintCurrentSnapshotInfo(ERROR, xid, snapshot, action)
|
|
#endif
|
|
|
|
/* local functions */
|
|
static bool XidVisibleInSnapshot(TransactionId xid, Snapshot snapshot, TransactionIdStatus* hintstatus);
|
|
static bool XidVisibleInLocalSnapshot(TransactionId xid, Snapshot snapshot);
|
|
static bool CommittedXidVisibleInSnapshot(TransactionId xid, Snapshot snapshot);
|
|
static bool TransactionIdIsPreparedAtSnapshot(TransactionId xid, Snapshot snapshot);
|
|
static bool IsXidVisibleInGtmLiteLocalSnapshot(TransactionId xid, Snapshot snapshot,
|
|
TransactionIdStatus hint_status, bool xmin_equal_xmax);
|
|
|
|
/* Log SetHintBits() */
|
|
static inline void LogSetHintBit(HeapTupleHeader tuple, Buffer buffer, uint16 infomask)
|
|
{
|
|
switch (infomask) {
|
|
case HEAP_XMIN_COMMITTED:
|
|
ereport(LOG,
|
|
(errmsg("Set Hint Bits: set oid %u infomask %s. Tuple Xmin : " XID_FMT ", Xmax : " XID_FMT ".",
|
|
HeapTupleHeaderGetOid(tuple),
|
|
"HEAP_XMIN_COMMITTED",
|
|
HeapTupleHeaderGetXmin(BufferGetPage(buffer), tuple),
|
|
HeapTupleHeaderGetXmax(BufferGetPage(buffer), tuple))));
|
|
break;
|
|
case HEAP_XMIN_INVALID:
|
|
ereport(LOG,
|
|
(errmsg("Set Hint Bits: set oid %u infomask %s. Tuple Xmin : " XID_FMT ", Xmax : " XID_FMT ".",
|
|
HeapTupleHeaderGetOid(tuple),
|
|
"HEAP_XMIN_INVALID",
|
|
HeapTupleHeaderGetXmin(BufferGetPage(buffer), tuple),
|
|
HeapTupleHeaderGetXmax(BufferGetPage(buffer), tuple))));
|
|
break;
|
|
case HEAP_XMAX_COMMITTED:
|
|
ereport(LOG,
|
|
(errmsg("Set Hint Bits: set oid %u infomask %s. Tuple Xmin : " XID_FMT ", Xmax : " XID_FMT ".",
|
|
HeapTupleHeaderGetOid(tuple),
|
|
"HEAP_XMAX_COMMITTED",
|
|
HeapTupleHeaderGetXmin(BufferGetPage(buffer), tuple),
|
|
HeapTupleHeaderGetXmax(BufferGetPage(buffer), tuple))));
|
|
break;
|
|
case HEAP_XMAX_INVALID:
|
|
ereport(LOG,
|
|
(errmsg("Set Hint Bits: set oid %u infomask %s. Tuple Xmin : " XID_FMT ", Xmax : " XID_FMT ".",
|
|
HeapTupleHeaderGetOid(tuple),
|
|
"HEAP_XMAX_INVALID",
|
|
HeapTupleHeaderGetXmin(BufferGetPage(buffer), tuple),
|
|
HeapTupleHeaderGetXmax(BufferGetPage(buffer), tuple))));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* SetHintBits()
|
|
*
|
|
* Set commit/abort hint bits on a tuple, if appropriate at this time.
|
|
*
|
|
* It is only safe to set a transaction-committed hint bit if we know the
|
|
* transaction's commit record has been flushed to disk, or if the table is
|
|
* temporary or unlogged and will be obliterated by a crash anyway. We
|
|
* cannot change the LSN of the page here because we may hold only a share
|
|
* lock on the buffer, so we can't use the LSN to interlock this; we have to
|
|
* just refrain from setting the hint bit until some future re-examination
|
|
* of the tuple.
|
|
*
|
|
* We can always set hint bits when marking a transaction aborted. (Some
|
|
* code in heapam.c relies on that!)
|
|
*
|
|
* Also, if we are cleaning up HEAP_MOVED_IN or HEAP_MOVED_OFF entries, then
|
|
* we can always set the hint bits, since pre-9.0 VACUUM FULL always used
|
|
* synchronous commits and didn't move tuples that weren't previously
|
|
* hinted. (This is not known by this subroutine, but is applied by its
|
|
* callers.) Note: old-style VACUUM FULL is gone, but we have to keep this
|
|
* module's support for MOVED_OFF/MOVED_IN flag bits for as long as we
|
|
* support in-place update from pre-9.0 databases.
|
|
*
|
|
* Normal commits may be asynchronous, so for those we need to get the LSN
|
|
* of the transaction and then check whether this is flushed.
|
|
*
|
|
* The caller should pass xid as the XID of the transaction to check, or
|
|
* InvalidTransactionId if no check is needed.
|
|
*/
|
|
static inline void SetHintBits(HeapTupleHeader tuple, Buffer buffer, uint16 infomask, TransactionId xid)
|
|
{
|
|
#ifdef PGXC
|
|
// The following scenario may use local snapshot, so do not set hint bits.
|
|
// Notice: we don't support two or more bits within infomask.
|
|
//
|
|
Assert(infomask > 0);
|
|
Assert(0 == (infomask & (infomask - 1)));
|
|
if ((t_thrd.xact_cxt.useLocalSnapshot && !(infomask & HEAP_XMIN_COMMITTED)) || RecoveryInProgress() ||
|
|
g_instance.attr.attr_storage.IsRoachStandbyCluster) {
|
|
ereport(DEBUG2, (errmsg("ignore setting tuple hint bits when local snapshot is used.")));
|
|
return;
|
|
}
|
|
|
|
if (XACT_READ_UNCOMMITTED == u_sess->utils_cxt.XactIsoLevel && !(infomask & HEAP_XMIN_COMMITTED)) {
|
|
ereport(DEBUG2, (errmsg("ignore setting tuple hint bits when XACT_READ_UNCOMMITTED is used.")));
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (TransactionIdIsValid(xid)) {
|
|
/* NB: xid must be known committed here! */
|
|
XLogRecPtr commitLSN = TransactionIdGetCommitLSN(xid);
|
|
|
|
if (BufferIsPermanent(buffer) && XLogNeedsFlush(commitLSN) && XLByteLT(BufferGetLSNAtomic(buffer), commitLSN)) {
|
|
/* not flushed and no LSN interlock, so don't set hint */
|
|
return;
|
|
}
|
|
}
|
|
|
|
tuple->t_infomask |= infomask;
|
|
MarkBufferDirtyHint(buffer, true);
|
|
|
|
#ifdef USE_ASSERT_CHECKING
|
|
if (u_sess->attr.attr_storage.enable_debug_vacuum && HeapTupleHeaderHasOid(tuple))
|
|
LogSetHintBit(tuple, buffer, infomask);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* HeapTupleSetHintBits --- exported version of SetHintBits()
|
|
*
|
|
* This must be separate because of C99's brain-dead notions about how to
|
|
* implement inline functions.
|
|
*/
|
|
void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer, uint16 infomask, TransactionId xid)
|
|
{
|
|
SetHintBits(tuple, buffer, infomask, xid);
|
|
}
|
|
|
|
/*
|
|
* HeapTupleSatisfiesSelf
|
|
* True iff heap tuple is valid "for itself".
|
|
*
|
|
* Here, we consider the effects of:
|
|
* all committed transactions (as of the current instant)
|
|
* previous commands of this transaction
|
|
* changes made by the current command
|
|
*
|
|
* Note:
|
|
* Assumes heap tuple is valid.
|
|
*
|
|
* The satisfaction of "itself" requires the following:
|
|
*
|
|
* ((Xmin == my-transaction && the row was updated by the current transaction, and
|
|
* (Xmax is null it was not deleted
|
|
* [|| Xmax != my-transaction)]) [or it was deleted by another transaction]
|
|
* ||
|
|
*
|
|
* (Xmin is committed && the row was modified by a committed transaction, and
|
|
* (Xmax is null || the row has not been deleted, or
|
|
* (Xmax != my-transaction && the row was deleted by another transaction
|
|
* Xmax is not committed))) that has not been committed
|
|
*/
|
|
bool HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
|
|
{
|
|
HeapTupleHeader tuple = htup->t_data;
|
|
Assert(ItemPointerIsValid(&htup->t_self));
|
|
Assert(htup->t_tableOid != InvalidOid);
|
|
/* do not need sync, because snapshot is not used */
|
|
Page page = BufferGetPage(buffer);
|
|
|
|
ereport(DEBUG1,
|
|
(errmsg("HeapTupleSatisfiesSelf self(%u,%u) ctid(%u,%u) cur_xid " XID_FMT " xmin " XID_FMT
|
|
" xmax " XID_FMT " csn " CSN_FMT,
|
|
ItemPointerGetBlockNumber(&htup->t_self),
|
|
ItemPointerGetOffsetNumber(&htup->t_self),
|
|
ItemPointerGetBlockNumber(&tuple->t_ctid),
|
|
ItemPointerGetOffsetNumber(&tuple->t_ctid),
|
|
GetCurrentTransactionIdIfAny(),
|
|
HeapTupleHeaderGetXmin(page, tuple),
|
|
HeapTupleHeaderGetXmax(page, tuple),
|
|
snapshot->snapshotcsn)));
|
|
|
|
if (!HeapTupleHeaderXminCommitted(tuple)) {
|
|
if (HeapTupleHeaderXminInvalid(tuple))
|
|
return false;
|
|
|
|
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(page, tuple))) {
|
|
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
|
|
return true;
|
|
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
|
|
return true;
|
|
|
|
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
|
|
|
|
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
/* deleting subtransaction must have aborted */
|
|
Assert(!TransactionIdDidCommit(HeapTupleHeaderGetXmax(page, tuple)));
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
} else if (TransactionIdIsInProgress(HeapTupleHeaderGetXmin(page, tuple)))
|
|
return false;
|
|
else if (TransactionIdDidCommit(HeapTupleHeaderGetXmin(page, tuple)))
|
|
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, HeapTupleHeaderGetXmin(page, tuple));
|
|
else {
|
|
if (!LatestFetchTransactionIdDidAbort(HeapTupleHeaderGetXmin(page, tuple)))
|
|
LatestTransactionStatusError(HeapTupleHeaderGetXmin(page, tuple),
|
|
NULL,
|
|
"HeapTupleSatisfiesSelf set HEAP_XMIN_INVALID xid don't abort");
|
|
/* it must have aborted or crashed */
|
|
SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* by here, the inserting transaction has committed */
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */
|
|
return true;
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_COMMITTED) {
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED)
|
|
return true;
|
|
return false; /* updated by other */
|
|
}
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) {
|
|
/* MultiXacts are currently only allowed to lock tuples */
|
|
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
|
|
return true;
|
|
}
|
|
|
|
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(page, tuple)))
|
|
return true;
|
|
|
|
if (!TransactionIdDidCommit(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
if (!LatestFetchTransactionIdDidAbort(HeapTupleHeaderGetXmax(page, tuple)))
|
|
LatestTransactionStatusError(HeapTupleHeaderGetXmax(page, tuple),
|
|
NULL,
|
|
"HeapTupleSatisfiesSelf set HEAP_XMAX_INVALID xid don't abort");
|
|
/* it must have aborted or crashed */
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
return true;
|
|
}
|
|
|
|
/* xmax transaction committed */
|
|
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED) {
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
return true;
|
|
}
|
|
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleHeaderGetXmax(page, tuple));
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* HeapTupleSatisfiesNow
|
|
* True iff heap tuple is valid "now".
|
|
*
|
|
* Here, we consider the effects of:
|
|
* all committed transactions (as of the current instant)
|
|
* previous commands of this transaction
|
|
*
|
|
* Note we do _not_ include changes made by the current command. This
|
|
* solves the "Halloween problem" wherein an UPDATE might try to re-update
|
|
* its own output tuples, http://en.wikipedia.org/wiki/Halloween_Problem.
|
|
*
|
|
* Note:
|
|
* Assumes heap tuple is valid.
|
|
*
|
|
* The satisfaction of "now" requires the following:
|
|
*
|
|
* ((Xmin == my-transaction && inserted by the current transaction
|
|
* Cmin < my-command && before this command, and
|
|
* (Xmax is null || the row has not been deleted, or
|
|
* (Xmax == my-transaction && it was deleted by the current transaction
|
|
* Cmax >= my-command))) but not before this command,
|
|
* || or
|
|
* (Xmin is committed && the row was inserted by a committed transaction, and
|
|
* (Xmax is null || the row has not been deleted, or
|
|
* (Xmax == my-transaction && the row is being deleted by this transaction
|
|
* Cmax >= my-command) || but it's not deleted "yet", or
|
|
* (Xmax != my-transaction && the row was deleted by another transaction
|
|
* Xmax is not committed)))) that has not been committed
|
|
*
|
|
*/
|
|
bool HeapTupleSatisfiesNow(HeapTuple htup, Snapshot snapshot, Buffer buffer)
|
|
{
|
|
HeapTupleHeader tuple = htup->t_data;
|
|
Assert(ItemPointerIsValid(&htup->t_self));
|
|
Assert(htup->t_tableOid != InvalidOid);
|
|
/* do not need sync, because snapshot is not used */
|
|
Page page = BufferGetPage(buffer);
|
|
|
|
ereport(DEBUG1,
|
|
(errmsg("HeapTupleSatisfiesNow self(%u,%u) ctid (%u,%u) cur_xid " XID_FMT " xmin " XID_FMT " xmax " XID_FMT,
|
|
ItemPointerGetBlockNumber(&htup->t_self),
|
|
ItemPointerGetOffsetNumber(&htup->t_self),
|
|
ItemPointerGetBlockNumber(&tuple->t_ctid),
|
|
ItemPointerGetOffsetNumber(&tuple->t_ctid),
|
|
GetCurrentTransactionIdIfAny(),
|
|
HeapTupleHeaderGetXmin(page, tuple),
|
|
HeapTupleHeaderGetXmax(page, tuple))));
|
|
|
|
if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) {
|
|
if (tuple->t_infomask & HEAP_XMIN_INVALID)
|
|
return false;
|
|
|
|
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(page, tuple))) {
|
|
if (HeapTupleHeaderGetCmin(tuple, page) >= GetCurrentCommandId(false))
|
|
return false; /* inserted after scan started */
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
|
|
return true;
|
|
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
|
|
return true;
|
|
|
|
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
|
|
|
|
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
/* deleting subtransaction must have aborted */
|
|
Assert(!TransactionIdDidCommit(HeapTupleHeaderGetXmax(page, tuple)));
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
return true;
|
|
}
|
|
|
|
if (HeapTupleHeaderGetCmax(tuple, page) >= GetCurrentCommandId(false))
|
|
return true; /* deleted after scan started */
|
|
else
|
|
return false; /* deleted before scan started */
|
|
} else if (TransactionIdIsInProgress(HeapTupleHeaderGetXmin(page, tuple))) {
|
|
return false;
|
|
} else if (TransactionIdDidCommit(HeapTupleHeaderGetXmin(page, tuple)))
|
|
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, HeapTupleHeaderGetXmin(page, tuple));
|
|
else {
|
|
if (!LatestFetchTransactionIdDidAbort(HeapTupleHeaderGetXmin(page, tuple)))
|
|
LatestTransactionStatusError(HeapTupleHeaderGetXmin(page, tuple),
|
|
NULL,
|
|
"HeapTupleSatisfiesNow set HEAP_XMIN_INVALID xid don't abort");
|
|
|
|
/* it must have aborted or crashed */
|
|
SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* by here, the inserting transaction has committed */
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */
|
|
return true;
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_COMMITTED) {
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) {
|
|
/* MultiXacts are currently only allowed to lock tuples */
|
|
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
|
|
return true;
|
|
}
|
|
|
|
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED)
|
|
return true;
|
|
if (HeapTupleHeaderGetCmax(tuple, page) >= GetCurrentCommandId(false))
|
|
return true; /* deleted after scan started */
|
|
else
|
|
return false; /* deleted before scan started */
|
|
}
|
|
|
|
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
return true;
|
|
}
|
|
|
|
if (!TransactionIdDidCommit(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
if (!LatestFetchTransactionIdDidAbort(HeapTupleHeaderGetXmax(page, tuple)))
|
|
LatestTransactionStatusError(HeapTupleHeaderGetXmax(page, tuple),
|
|
NULL,
|
|
"HeapTupleSatisfiesNow set HEAP_XMAX_INVALID xid don't abort");
|
|
|
|
/* it must have aborted or crashed */
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
return true;
|
|
}
|
|
|
|
/* xmax transaction committed */
|
|
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED) {
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
return true;
|
|
}
|
|
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleHeaderGetXmax(page, tuple));
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* HeapTupleSatisfiesAny
|
|
* Dummy "satisfies" routine: any tuple satisfies SnapshotAny.
|
|
*/
|
|
bool HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* HeapTupleSatisfiesToast
|
|
* True iff heap tuple is valid as a TOAST row.
|
|
*
|
|
* This is a simplified version that only checks for VACUUM moving conditions.
|
|
* It's appropriate for TOAST usage because TOAST really doesn't want to do
|
|
* its own time qual checks; if you can see the main table row that contains
|
|
* a TOAST reference, you should be able to see the TOASTed value. However,
|
|
* vacuuming a TOAST table is independent of the main table, and in case such
|
|
* a vacuum fails partway through, we'd better do this much checking.
|
|
*
|
|
* Among other things, this means you can't do UPDATEs of rows in a TOAST
|
|
* table.
|
|
*/
|
|
bool HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot, Buffer buffer)
|
|
{
|
|
HeapTupleHeader tuple = htup->t_data;
|
|
Assert(ItemPointerIsValid(&htup->t_self));
|
|
Assert(htup->t_tableOid != InvalidOid);
|
|
/* do not need sync, because snapshot is not used */
|
|
|
|
if (!HeapTupleHeaderXminCommitted(tuple)) {
|
|
if (HeapTupleHeaderXminInvalid(tuple))
|
|
return false;
|
|
|
|
/*
|
|
* An invalid Xmin can be left behind by a speculative insertion that
|
|
* is cancelled by super-deleting the tuple. We shouldn't see any of
|
|
* those in TOAST tables, but better safe than sorry.
|
|
*/
|
|
if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(BufferGetPage(buffer), tuple)))
|
|
return false;
|
|
}
|
|
|
|
/* otherwise assume the tuple is valid for TOAST. */
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* HeapTupleSatisfiesUpdate
|
|
*
|
|
* Same logic as HeapTupleSatisfiesNow, but returns a more detailed result
|
|
* code, since UPDATE needs to know more than "is it visible?". Also,
|
|
* tuples of my own xact are tested against the passed CommandId not
|
|
* CurrentCommandId.
|
|
*
|
|
* The possible return codes are:
|
|
*
|
|
* HeapTupleInvisible: the tuple didn't exist at all when the scan started,
|
|
* e.g. it was created by a later CommandId.
|
|
*
|
|
* HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be
|
|
* updated.
|
|
*
|
|
* HeapTupleSelfUpdated: The tuple was updated by the current transaction,
|
|
* after the current scan started.
|
|
*
|
|
* HeapTupleUpdated: The tuple was updated by a committed transaction.
|
|
*
|
|
* HeapTupleBeingUpdated: The tuple is being updated by an in-progress
|
|
* transaction other than the current transaction. (Note: this includes
|
|
* the case where the tuple is share-locked by a MultiXact, even if the
|
|
* MultiXact includes the current transaction. Callers that want to
|
|
* distinguish that case must test for it themselves.)
|
|
*
|
|
* HeapTupleSelfCreated: the tuple didn't exist at all when the scan started, it
|
|
* was created during the current CommandId (scan)
|
|
*/
|
|
HTSU_Result HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, Buffer buffer, bool self_visible)
|
|
{
|
|
bool needSync = false;
|
|
HeapTupleHeader tuple = htup->t_data;
|
|
Assert(ItemPointerIsValid(&htup->t_self));
|
|
Assert(htup->t_tableOid != InvalidOid);
|
|
Page page = BufferGetPage(buffer);
|
|
|
|
/* do not need sync, because snapshot is not used */
|
|
ereport(DEBUG1,
|
|
(errmsg("HeapTupleSatisfiesUpdate self(%u,%u) ctid(%u,%u) cur_xid " XID_FMT " xmin"
|
|
XID_FMT " xmax " XID_FMT " infomask %u",
|
|
ItemPointerGetBlockNumber(&htup->t_self),
|
|
ItemPointerGetOffsetNumber(&htup->t_self),
|
|
ItemPointerGetBlockNumber(&tuple->t_ctid),
|
|
ItemPointerGetOffsetNumber(&tuple->t_ctid),
|
|
GetCurrentTransactionIdIfAny(),
|
|
HeapTupleHeaderGetXmin(page, tuple),
|
|
HeapTupleHeaderGetXmax(page, tuple),
|
|
tuple->t_infomask)));
|
|
|
|
restart:
|
|
if (!HeapTupleHeaderXminCommitted(tuple)) {
|
|
if (HeapTupleHeaderXminInvalid(tuple))
|
|
return HeapTupleInvisible;
|
|
|
|
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(page, tuple))) {
|
|
if (HeapTupleHeaderGetCmin(tuple, page) > curcid)
|
|
return HeapTupleInvisible; /* inserted after scan started */
|
|
else if (HeapTupleHeaderGetCmin(tuple, page) == curcid && !self_visible)
|
|
return HeapTupleSelfCreated; /* inserted during the scan */
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
|
|
return HeapTupleMayBeUpdated;
|
|
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
|
|
return HeapTupleMayBeUpdated;
|
|
|
|
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
|
|
|
|
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
/* deleting subtransaction must have aborted */
|
|
Assert(!TransactionIdDidCommit(HeapTupleHeaderGetXmax(page, tuple)));
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
return HeapTupleMayBeUpdated;
|
|
}
|
|
|
|
if (HeapTupleHeaderGetCmax(tuple, page) >= curcid)
|
|
return HeapTupleSelfUpdated; /* updated after scan started */
|
|
else
|
|
return HeapTupleInvisible; /* updated before scan started */
|
|
} else if (TransactionIdIsInProgress(HeapTupleHeaderGetXmin(page, tuple), &needSync, false, false)) {
|
|
if (needSync) {
|
|
needSync = false;
|
|
SyncLocalXidWait(HeapTupleHeaderGetXmin(page, tuple));
|
|
goto restart;
|
|
}
|
|
return HeapTupleInvisible;
|
|
} else if (TransactionIdDidCommit(HeapTupleHeaderGetXmin(page, tuple)))
|
|
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, HeapTupleHeaderGetXmin(page, tuple));
|
|
else {
|
|
if (!LatestFetchTransactionIdDidAbort(HeapTupleHeaderGetXmin(page, tuple)))
|
|
LatestTransactionStatusError(HeapTupleHeaderGetXmin(page, tuple),
|
|
NULL,
|
|
"HeapTupleSatisfiesUpdate set HEAP_XMIN_INVALID xid don't abort");
|
|
/* it must have aborted or crashed */
|
|
SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId);
|
|
return HeapTupleInvisible;
|
|
}
|
|
}
|
|
|
|
/* by here, the inserting transaction has committed */
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */
|
|
return HeapTupleMayBeUpdated;
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_COMMITTED) {
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED)
|
|
return HeapTupleMayBeUpdated;
|
|
return HeapTupleUpdated; /* updated by other */
|
|
}
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) {
|
|
/* MultiXacts are currently only allowed to lock tuples */
|
|
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
|
|
|
|
if (MultiXactIdIsRunning(HeapTupleHeaderGetXmax(page, tuple)))
|
|
return HeapTupleBeingUpdated;
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
return HeapTupleMayBeUpdated;
|
|
}
|
|
|
|
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED)
|
|
return HeapTupleMayBeUpdated;
|
|
if (HeapTupleHeaderGetCmax(tuple, page) >= curcid)
|
|
return HeapTupleSelfUpdated; /* updated after scan started */
|
|
else
|
|
return HeapTupleInvisible; /* updated before scan started */
|
|
}
|
|
|
|
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(page, tuple)))
|
|
return HeapTupleBeingUpdated;
|
|
|
|
if (!TransactionIdDidCommit(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
if (!LatestFetchTransactionIdDidAbort(HeapTupleHeaderGetXmax(page, tuple)))
|
|
LatestTransactionStatusError(HeapTupleHeaderGetXmax(page, tuple),
|
|
NULL,
|
|
"HeapTupleSatisfiesUpdate set HEAP_XMAX_INVALID xid don't abort");
|
|
/* it must have aborted or crashed */
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
return HeapTupleMayBeUpdated;
|
|
}
|
|
|
|
/* xmax transaction committed */
|
|
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED) {
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
return HeapTupleMayBeUpdated;
|
|
}
|
|
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleHeaderGetXmax(page, tuple));
|
|
return HeapTupleUpdated; /* updated by other */
|
|
}
|
|
|
|
/*
|
|
* HeapTupleSatisfiesDirty
|
|
* True iff heap tuple is valid including effects of open transactions.
|
|
*
|
|
* Here, we consider the effects of:
|
|
* all committed and in-progress transactions (as of the current instant)
|
|
* previous commands of this transaction
|
|
* changes made by the current command
|
|
*
|
|
* This is essentially like HeapTupleSatisfiesSelf as far as effects of
|
|
* the current transaction and committed/aborted xacts are concerned.
|
|
* However, we also include the effects of other xacts still in progress.
|
|
*
|
|
* A special hack is that the passed-in snapshot struct is used as an
|
|
* output argument to return the xids of concurrent xacts that affected the
|
|
* tuple. snapshot->xmin is set to the tuple's xmin if that is another
|
|
* transaction that's still in progress; or to InvalidTransactionId if the
|
|
* tuple's xmin is committed good, committed dead, or my own xact. Similarly
|
|
* for snapshot->xmax and the tuple's xmax.
|
|
*/
|
|
bool HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot, Buffer buffer)
|
|
{
|
|
HeapTupleHeader tuple = htup->t_data;
|
|
Assert(ItemPointerIsValid(&htup->t_self));
|
|
Assert(htup->t_tableOid != InvalidOid);
|
|
/* do not need sync, because snapshot is not used */
|
|
Page page = BufferGetPage(buffer);
|
|
|
|
ereport(DEBUG1,
|
|
(errmsg("HeapTupleSatisfiesDirty self(%u,%u) ctid(%u,%u) cur_xid " XID_FMT " xmin"
|
|
XID_FMT " xmax " XID_FMT " infomask %u",
|
|
ItemPointerGetBlockNumber(&htup->t_self),
|
|
ItemPointerGetOffsetNumber(&htup->t_self),
|
|
ItemPointerGetBlockNumber(&tuple->t_ctid),
|
|
ItemPointerGetOffsetNumber(&tuple->t_ctid),
|
|
GetCurrentTransactionIdIfAny(),
|
|
HeapTupleHeaderGetXmin(page, tuple),
|
|
HeapTupleHeaderGetXmax(page, tuple),
|
|
tuple->t_infomask)));
|
|
|
|
snapshot->xmin = snapshot->xmax = InvalidTransactionId;
|
|
|
|
if (!HeapTupleHeaderXminCommitted(tuple)) {
|
|
if (HeapTupleHeaderXminInvalid(tuple))
|
|
return false;
|
|
|
|
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(page, tuple))) {
|
|
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
|
|
return true;
|
|
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
|
|
return true;
|
|
|
|
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
|
|
|
|
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
/* deleting subtransaction must have aborted */
|
|
Assert(!TransactionIdDidCommit(HeapTupleHeaderGetXmax(page, tuple)));
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
} else if (TransactionIdIsInProgress(HeapTupleHeaderGetXmin(page, tuple))) {
|
|
snapshot->xmin = HeapTupleHeaderGetXmin(page, tuple);
|
|
/* XXX shouldn't we fall through to look at xmax? */
|
|
return true; /* in insertion by other */
|
|
} else if (TransactionIdDidCommit(HeapTupleHeaderGetXmin(page, tuple)))
|
|
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, HeapTupleHeaderGetXmin(page, tuple));
|
|
else {
|
|
if (!LatestFetchTransactionIdDidAbort(HeapTupleHeaderGetXmin(page, tuple)))
|
|
LatestTransactionStatusError(HeapTupleHeaderGetXmin(page, tuple),
|
|
NULL,
|
|
"HeapTupleSatisfiesDirty set HEAP_XMIN_INVALID xid don't abort");
|
|
/* it must have aborted or crashed */
|
|
SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* by here, the inserting transaction has committed */
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */
|
|
return true;
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_COMMITTED) {
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED)
|
|
return true;
|
|
return false; /* updated by other */
|
|
}
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) {
|
|
/* MultiXacts are currently only allowed to lock tuples */
|
|
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
|
|
return true;
|
|
}
|
|
|
|
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
snapshot->xmax = HeapTupleHeaderGetXmax(page, tuple);
|
|
return true;
|
|
}
|
|
|
|
if (!TransactionIdDidCommit(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
if (!LatestFetchTransactionIdDidAbort(HeapTupleHeaderGetXmax(page, tuple)))
|
|
LatestTransactionStatusError(HeapTupleHeaderGetXmax(page, tuple),
|
|
NULL,
|
|
"HeapTupleSatisfiesDirty set HEAP_XMAX_INVALID xid don't abort");
|
|
/* it must have aborted or crashed */
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
return true;
|
|
}
|
|
|
|
/* xmax transaction committed */
|
|
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED) {
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
return true;
|
|
}
|
|
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleHeaderGetXmax(page, tuple));
|
|
return false; /* updated by other */
|
|
}
|
|
|
|
/*
|
|
* TransactionIdIsPreparedAtSnapshot
|
|
* At the time of snapshot, true iff transaction associated
|
|
* with the identifier is prepared for two-phase commit
|
|
*
|
|
* Note: only gxacts marked "valid" are considered; but notice we do not
|
|
* check the locking status.
|
|
*
|
|
*/
|
|
static bool TransactionIdIsPreparedAtSnapshot(TransactionId xid, Snapshot snapshot)
|
|
{
|
|
for (int i = 0; i < snapshot->prepared_count; i++) {
|
|
if (xid == snapshot->prepared_array[i]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool IsXidVisibleInGtmLiteLocalSnapshot(TransactionId xid, Snapshot snapshot,
|
|
TransactionIdStatus hint_status, bool xmin_equal_xmax)
|
|
{
|
|
/*
|
|
* calling XidVisibleInSnapshot(), it will determine that if it is visible correctly.
|
|
* However, because we support mixed snapshots (global and local), we have to
|
|
* examine if the xmin is in prepared list or not. Even if it is "invisible",
|
|
* but if it is in the list, we will treat it as visible (if the transaction is NOT aborted at the end).
|
|
* exceptional case for xmin == xmax when check xmin
|
|
* xmin_qual_xmax always false when check xmax
|
|
*/
|
|
if (hint_status == XID_ABORTED || xmin_equal_xmax) {
|
|
return false;
|
|
}
|
|
|
|
if (TransactionIdIsPreparedAtSnapshot(xid, snapshot)) {
|
|
if (hint_status == XID_COMMITTED) {
|
|
return true;
|
|
}
|
|
/* use newest prepared list to check */
|
|
while (IsTransactionIdMarkedPrepared(xid)) {
|
|
SyncLocalXidWait(xid);
|
|
}
|
|
/* should we always use clog ? */
|
|
if (TransactionIdDidCommit(xid) == true) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* HeapTupleSatisfiesMVCC
|
|
* True iff heap tuple is valid for the given MVCC snapshot.
|
|
*
|
|
* Here, we consider the effects of:
|
|
* all transactions committed as of the time of the given snapshot
|
|
* previous commands of this transaction
|
|
*
|
|
* Does _not_ include:
|
|
* transactions shown as in-progress by the snapshot
|
|
* transactions started after the snapshot was taken
|
|
* changes made by the current command
|
|
*
|
|
* This is the same as HeapTupleSatisfiesNow, except that transactions that
|
|
* were in progress or as yet unstarted when the snapshot was taken will
|
|
* be treated as uncommitted, even if they have committed by now.
|
|
*
|
|
* (Notice, however, that the tuple status hint bits will be updated on the
|
|
* basis of the true state of the transaction, even if we then pretend we
|
|
* can't see it.)
|
|
*/
|
|
bool HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot, Buffer buffer)
|
|
{
|
|
HeapTupleHeader tuple = htup->t_data;
|
|
Assert(ItemPointerIsValid(&htup->t_self));
|
|
Assert(htup->t_tableOid != InvalidOid);
|
|
bool visible = false;
|
|
TransactionIdStatus hintstatus;
|
|
Page page = BufferGetPage(buffer);
|
|
|
|
ereport(DEBUG1,
|
|
(errmsg("HeapTupleSatisfiesMVCC self(%u,%u) ctid(%u,%u) cur_xid " XID_FMT " xmin " XID_FMT
|
|
" xmax " XID_FMT " csn " CSN_FMT,
|
|
ItemPointerGetBlockNumber(&htup->t_self),
|
|
ItemPointerGetOffsetNumber(&htup->t_self),
|
|
ItemPointerGetBlockNumber(&tuple->t_ctid),
|
|
ItemPointerGetOffsetNumber(&tuple->t_ctid),
|
|
GetCurrentTransactionIdIfAny(),
|
|
HeapTupleHeaderGetXmin(page, tuple),
|
|
HeapTupleHeaderGetXmax(page, tuple),
|
|
snapshot->snapshotcsn)));
|
|
|
|
/*
|
|
* Just valid for read-only transaction when u_sess->attr.attr_common.XactReadOnly is true.
|
|
* Show any tuples including dirty ones when u_sess->attr.attr_storage.enable_show_any_tuples is true.
|
|
* GUC param u_sess->attr.attr_storage.enable_show_any_tuples is just for analyse or maintenance
|
|
*/
|
|
if (u_sess->attr.attr_common.XactReadOnly && u_sess->attr.attr_storage.enable_show_any_tuples)
|
|
return true;
|
|
|
|
if (!HeapTupleHeaderXminCommitted(tuple)) {
|
|
if (HeapTupleHeaderXminInvalid(tuple))
|
|
return false;
|
|
|
|
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(page, tuple))) {
|
|
if ((tuple->t_infomask & HEAP_COMBOCID) && CheckStreamCombocid(tuple, snapshot->curcid, page))
|
|
return true; /* delete after stream producer thread scan started */
|
|
|
|
if (HeapTupleHeaderGetCmin(tuple, page) >= snapshot->curcid)
|
|
return false; /* inserted after scan started */
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
|
|
return true;
|
|
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
|
|
return true;
|
|
|
|
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
|
|
|
|
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
/* deleting subtransaction must have aborted */
|
|
Assert(!TransactionIdDidCommit(HeapTupleHeaderGetXmax(page, tuple)));
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
return true;
|
|
}
|
|
|
|
if (HeapTupleHeaderGetCmax(tuple, page) >= snapshot->curcid)
|
|
return true; /* deleted after scan started */
|
|
else
|
|
return false; /* deleted before scan started */
|
|
} else {
|
|
visible = XidVisibleInSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot, &hintstatus);
|
|
if (hintstatus == XID_COMMITTED)
|
|
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, HeapTupleHeaderGetXmin(page, tuple));
|
|
|
|
if (hintstatus == XID_ABORTED) {
|
|
if (!LatestFetchCSNDidAbort(HeapTupleHeaderGetXmin(page, tuple)))
|
|
LatestTransactionStatusError(HeapTupleHeaderGetXmin(page, tuple),
|
|
snapshot,
|
|
"HeapTupleSatisfiesMVCC set HEAP_XMIN_INVALID xid don't abort");
|
|
|
|
SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId);
|
|
}
|
|
|
|
if (!visible) {
|
|
if (!GTM_LITE_MODE || u_sess->attr.attr_common.xc_maintenance_mode ||
|
|
snapshot->snapshot_type != SNAPSHOT_TYPE_LOCAL ||
|
|
!IsXidVisibleInGtmLiteLocalSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot, hintstatus,
|
|
HeapTupleHeaderGetXmin(page, tuple) == HeapTupleHeaderGetXmax(page, tuple))) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
/* xmin is committed, but maybe not according to our snapshot */
|
|
if (!HeapTupleHeaderXminFrozen(tuple) &&
|
|
!CommittedXidVisibleInSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot)) {
|
|
/* tuple xmin has already committed, no need to use xc_maintenance_mod bypass */
|
|
if (!GTM_LITE_MODE || snapshot->snapshot_type != SNAPSHOT_TYPE_LOCAL ||
|
|
!IsXidVisibleInGtmLiteLocalSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot, XID_COMMITTED,
|
|
HeapTupleHeaderGetXmin(page, tuple) == HeapTupleHeaderGetXmax(page, tuple))) {
|
|
return false; /* treat as still in progress */
|
|
}
|
|
}
|
|
}
|
|
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */
|
|
return true;
|
|
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED)
|
|
return true;
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) {
|
|
/* MultiXacts are currently only allowed to lock tuples */
|
|
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
|
|
return true;
|
|
}
|
|
|
|
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) {
|
|
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
if (HeapTupleHeaderGetCmax(tuple, page) >= snapshot->curcid)
|
|
return true; /* deleted after scan started */
|
|
else
|
|
return false; /* deleted before scan started */
|
|
}
|
|
|
|
visible = XidVisibleInSnapshot(HeapTupleHeaderGetXmax(page, tuple), snapshot, &hintstatus);
|
|
if (hintstatus == XID_COMMITTED) {
|
|
/* xmax transaction committed */
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleHeaderGetXmax(page, tuple));
|
|
}
|
|
if (hintstatus == XID_ABORTED) {
|
|
if (!LatestFetchCSNDidAbort(HeapTupleHeaderGetXmax(page, tuple)))
|
|
LatestTransactionStatusError(HeapTupleHeaderGetXmax(page, tuple),
|
|
snapshot,
|
|
"HeapTupleSatisfiesMVCC set HEAP_XMAX_INVALID xid don't abort");
|
|
|
|
/* it must have aborted or crashed */
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
}
|
|
if (!visible) {
|
|
if (!GTM_LITE_MODE || u_sess->attr.attr_common.xc_maintenance_mode ||
|
|
snapshot->snapshot_type != SNAPSHOT_TYPE_LOCAL ||
|
|
!IsXidVisibleInGtmLiteLocalSnapshot(HeapTupleHeaderGetXmax(page, tuple),
|
|
snapshot, hintstatus, false)) {
|
|
return true; /* treat as still in progress */
|
|
}
|
|
}
|
|
} else {
|
|
/* xmax is committed, but maybe not according to our snapshot */
|
|
if (!CommittedXidVisibleInSnapshot(HeapTupleHeaderGetXmax(page, tuple), snapshot)) {
|
|
if (!GTM_LITE_MODE || snapshot->snapshot_type != SNAPSHOT_TYPE_LOCAL ||
|
|
!IsXidVisibleInGtmLiteLocalSnapshot(HeapTupleHeaderGetXmax(page, tuple),
|
|
snapshot, XID_COMMITTED, false)) {
|
|
return true; /* treat as still in progress */
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* HeapTupleSatisfiesLocalMVCC
|
|
* True iff heap tuple is valid for the given local MVCC snapshot.
|
|
*
|
|
* Here, we consider the effects of:
|
|
* all transactions local committed as of the time of the given snapshot
|
|
* previous commands of this transaction
|
|
*
|
|
* Does _not_ include:
|
|
* transactions committed in gtm but not committed locally
|
|
* transactions shown as in-progress by the snapshot
|
|
* transactions started after the snapshot was taken
|
|
* changes made by the current command
|
|
*
|
|
* This is the same as HeapTupleSatisfiesNow, except that transactions that
|
|
* were in progress or as yet unstarted when the snapshot was taken will
|
|
* be treated as uncommitted, even if they have committed by now.
|
|
*
|
|
* (Notice, the tuple status hint bits will be not updated.)
|
|
*/
|
|
|
|
bool HeapTupleSatisfiesLocalMVCC(HeapTuple htup, Snapshot snapshot, Buffer buffer)
|
|
{
|
|
HeapTupleHeader tuple = htup->t_data;
|
|
Assert(ItemPointerIsValid(&htup->t_self));
|
|
Assert(htup->t_tableOid != InvalidOid);
|
|
bool visible = false;
|
|
Page page = BufferGetPage(buffer);
|
|
|
|
ereport(DEBUG1,
|
|
(errmsg("HeapTupleSatisfiesLocalMVCC ctid (%u,%u) cur_xid " XID_FMT " xmin " XID_FMT
|
|
" xmax " XID_FMT " csn " CSN_FMT,
|
|
ItemPointerGetBlockNumber(&tuple->t_ctid),
|
|
ItemPointerGetOffsetNumber(&tuple->t_ctid),
|
|
GetCurrentTransactionIdIfAny(),
|
|
HeapTupleHeaderGetXmin(page, tuple),
|
|
HeapTupleHeaderGetXmax(page, tuple),
|
|
snapshot->snapshotcsn)));
|
|
|
|
/*
|
|
* Just valid for read-only transaction when u_sess->attr.attr_common.XactReadOnly is true.
|
|
* Show any tuples including dirty ones when u_sess->attr.attr_storage.enable_show_any_tuples is true.
|
|
* GUC param u_sess->attr.attr_storage.enable_show_any_tuples is just for analyse or maintenance
|
|
*/
|
|
if (u_sess->attr.attr_common.XactReadOnly && u_sess->attr.attr_storage.enable_show_any_tuples)
|
|
return true;
|
|
|
|
if (!HeapTupleHeaderXminCommitted(tuple)) {
|
|
if (HeapTupleHeaderXminInvalid(tuple))
|
|
return false;
|
|
|
|
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(page, tuple))) {
|
|
if ((tuple->t_infomask & HEAP_COMBOCID) && CheckStreamCombocid(tuple, snapshot->curcid, page))
|
|
return true; /* delete after stream producer thread scan started */
|
|
|
|
if (HeapTupleHeaderGetCmin(tuple, page) >= snapshot->curcid)
|
|
return false; /* inserted after scan started */
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
|
|
return true;
|
|
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
|
|
return true;
|
|
|
|
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
|
|
|
|
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
return true;
|
|
}
|
|
|
|
if (HeapTupleHeaderGetCmax(tuple, page) >= snapshot->curcid)
|
|
return true; /* deleted after scan started */
|
|
else
|
|
return false; /* deleted before scan started */
|
|
} else {
|
|
visible = XidVisibleInLocalSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot);
|
|
|
|
if (!visible)
|
|
return false;
|
|
}
|
|
} else {
|
|
/* xmin is committed, but maybe not according to our snapshot */
|
|
if (!HeapTupleHeaderXminFrozen(tuple) &&
|
|
!XidVisibleInLocalSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot))
|
|
return false; /* treat as still in progress */
|
|
}
|
|
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */
|
|
return true;
|
|
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED)
|
|
return true;
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) {
|
|
/* MultiXacts are currently only allowed to lock tuples */
|
|
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
|
|
return true;
|
|
}
|
|
|
|
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) {
|
|
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) {
|
|
if (HeapTupleHeaderGetCmax(tuple, page) >= snapshot->curcid)
|
|
return true; /* deleted after scan started */
|
|
else
|
|
return false; /* deleted before scan started */
|
|
}
|
|
|
|
visible = XidVisibleInLocalSnapshot(HeapTupleHeaderGetXmax(page, tuple), snapshot);
|
|
if (!visible)
|
|
return true; /* treat as still in progress */
|
|
} else {
|
|
/* xmax is committed, but maybe not according to our snapshot */
|
|
if (!XidVisibleInLocalSnapshot(HeapTupleHeaderGetXmax(page, tuple), snapshot))
|
|
return true; /* treat as still in progress */
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* HeapTupleSatisfiesVacuum
|
|
*
|
|
* Determine the status of tuples for VACUUM purposes. Here, what
|
|
* we mainly want to know is if a tuple is potentially visible to *any*
|
|
* running transaction. If so, it can't be removed yet by VACUUM.
|
|
*
|
|
* OldestXmin is a cutoff XID (obtained from GetOldestXmin()). Tuples
|
|
* deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
|
|
* still be visible to some open transaction, so we can't remove them,
|
|
* even if we see that the deleting transaction has committed.
|
|
*/
|
|
HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin, Buffer buffer)
|
|
{
|
|
HeapTupleHeader tuple = htup->t_data;
|
|
TransactionIdStatus xidstatus;
|
|
Assert(ItemPointerIsValid(&htup->t_self));
|
|
Assert(htup->t_tableOid != InvalidOid);
|
|
|
|
/* do not need sync, because snapshot is not used */
|
|
Page page = BufferGetPage(buffer);
|
|
HeapTupleCopyBaseFromPage(htup, page);
|
|
|
|
if (SHOW_DEBUG_MESSAGE()) {
|
|
ereport(DEBUG1,
|
|
(errmsg("HeapTupleSatisfiesVacuum self(%u,%u) ctid(%u,%u) cur_xid " XID_FMT " xmin " XID_FMT
|
|
" xmax " XID_FMT " OldestXmin " XID_FMT,
|
|
ItemPointerGetBlockNumber(&htup->t_self),
|
|
ItemPointerGetOffsetNumber(&htup->t_self),
|
|
ItemPointerGetBlockNumber(&tuple->t_ctid),
|
|
ItemPointerGetOffsetNumber(&tuple->t_ctid),
|
|
GetCurrentTransactionIdIfAny(),
|
|
HeapTupleHeaderGetXmin(page, tuple),
|
|
HeapTupleHeaderGetXmax(page, tuple),
|
|
OldestXmin)));
|
|
}
|
|
|
|
/*
|
|
* Has inserting transaction committed?
|
|
*
|
|
* If the inserting transaction aborted, then the tuple was never visible
|
|
* to any other transaction, so we can delete it immediately.
|
|
*/
|
|
if (!HeapTupleHeaderXminCommitted(tuple)) {
|
|
if (HeapTupleHeaderXminInvalid(tuple))
|
|
return HEAPTUPLE_DEAD;
|
|
xidstatus = TransactionIdGetStatus(HeapTupleGetRawXmin(htup), false);
|
|
if (xidstatus == XID_INPROGRESS) {
|
|
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
|
|
return HEAPTUPLE_INSERT_IN_PROGRESS;
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED)
|
|
return HEAPTUPLE_INSERT_IN_PROGRESS;
|
|
/* inserted and then deleted by same xact */
|
|
return HEAPTUPLE_DELETE_IN_PROGRESS;
|
|
} else if (xidstatus == XID_COMMITTED) {
|
|
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, HeapTupleGetRawXmin(htup));
|
|
} else {
|
|
/*
|
|
* Not in Progress, Not Committed, so either Aborted or crashed
|
|
*/
|
|
if (u_sess->attr.attr_storage.enable_debug_vacuum && t_thrd.utils_cxt.pRelatedRel) {
|
|
elogVacuumInfo(
|
|
t_thrd.utils_cxt.pRelatedRel, htup, "HeapTupleSatisfiedVacuum set HEAP_XMIN_INVALID", OldestXmin);
|
|
}
|
|
if (!LatestFetchTransactionIdDidAbort(HeapTupleHeaderGetXmin(page, tuple)))
|
|
LatestTransactionStatusError(HeapTupleHeaderGetXmin(page, tuple),
|
|
NULL,
|
|
"HeapTupleSatisfiedVacuum set HEAP_XMIN_INVALID xid don't abort");
|
|
SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId);
|
|
return ((!t_thrd.xact_cxt.useLocalSnapshot || IsInitdb) ? HEAPTUPLE_DEAD : HEAPTUPLE_LIVE);
|
|
}
|
|
|
|
/*
|
|
* At this point the xmin is known committed, but we might not have
|
|
* been able to set the hint bit yet; so we can no longer Assert that
|
|
* it's set.
|
|
*/
|
|
}
|
|
|
|
/*
|
|
* Okay, the inserter committed, so it was good at some point. Now what
|
|
* about the deleting transaction?
|
|
*/
|
|
if (tuple->t_infomask & HEAP_XMAX_INVALID)
|
|
return HEAPTUPLE_LIVE;
|
|
|
|
if (tuple->t_infomask & HEAP_IS_LOCKED) {
|
|
/*
|
|
* "Deleting" xact really only locked it, so the tuple is live in any
|
|
* case. However, we should make sure that either XMAX_COMMITTED or
|
|
* XMAX_INVALID gets set once the xact is gone, to reduce the costs of
|
|
* examining the tuple for future xacts. Also, marking dead
|
|
* MultiXacts as invalid.
|
|
*/
|
|
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) {
|
|
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) {
|
|
if (MultiXactIdIsRunning(HeapTupleGetRawXmax(htup)))
|
|
return HEAPTUPLE_LIVE;
|
|
} else {
|
|
xidstatus = TransactionIdGetStatus(HeapTupleGetRawXmax(htup), false);
|
|
if (xidstatus == XID_INPROGRESS) {
|
|
return HEAPTUPLE_LIVE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We don't really care whether xmax did commit, abort or crash.
|
|
* We know that xmax did lock the tuple, but it did not and will
|
|
* never actually update it.
|
|
*/
|
|
if (u_sess->attr.attr_storage.enable_debug_vacuum && t_thrd.utils_cxt.pRelatedRel) {
|
|
elogVacuumInfo(
|
|
t_thrd.utils_cxt.pRelatedRel, htup, "HeapTupleSatisfiedVacuum set HEAP_XMAX_INVALID ", OldestXmin);
|
|
}
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
}
|
|
return HEAPTUPLE_LIVE;
|
|
}
|
|
|
|
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) {
|
|
/* MultiXacts are currently only allowed to lock tuples */
|
|
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
|
|
return HEAPTUPLE_LIVE;
|
|
}
|
|
|
|
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) {
|
|
xidstatus = TransactionIdGetStatus(HeapTupleGetRawXmax(htup), false);
|
|
if (xidstatus == XID_INPROGRESS)
|
|
return HEAPTUPLE_DELETE_IN_PROGRESS;
|
|
else if (xidstatus == XID_COMMITTED)
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleGetRawXmax(htup));
|
|
else {
|
|
/*
|
|
* Not in Progress, Not Committed, so either Aborted or crashed
|
|
*/
|
|
if (!LatestFetchTransactionIdDidAbort(HeapTupleHeaderGetXmax(page, tuple)))
|
|
LatestTransactionStatusError(HeapTupleHeaderGetXmax(page, tuple),
|
|
NULL,
|
|
"HeapTupleSatisfiedVacuum set HEAP_XMAX_INVALID xid don't abort");
|
|
|
|
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
|
|
return HEAPTUPLE_LIVE;
|
|
}
|
|
|
|
/*
|
|
* At this point the xmax is known committed, but we might not have
|
|
* been able to set the hint bit yet; so we can no longer Assert that
|
|
* it's set.
|
|
*/
|
|
}
|
|
|
|
/*
|
|
* Deleter committed, but perhaps it was recent enough that some open
|
|
* transactions could still see the tuple.
|
|
*/
|
|
if (!TransactionIdPrecedes(HeapTupleGetRawXmax(htup), OldestXmin))
|
|
return ((!t_thrd.xact_cxt.useLocalSnapshot || IsInitdb) ? HEAPTUPLE_RECENTLY_DEAD : HEAPTUPLE_LIVE);
|
|
|
|
/* Otherwise, it's dead and removable */
|
|
return ((!t_thrd.xact_cxt.useLocalSnapshot || IsInitdb) ? HEAPTUPLE_DEAD : HEAPTUPLE_LIVE);
|
|
}
|
|
|
|
/*
|
|
* HeapTupleIsSurelyDead
|
|
*
|
|
* Determine whether a tuple is surely dead. We sometimes use this
|
|
* in lieu of HeapTupleSatisifesVacuum when the tuple has just been
|
|
* tested by HeapTupleSatisfiesMVCC and, therefore, any hint bits that
|
|
* can be set should already be set. We assume that if no hint bits
|
|
* either for xmin or xmax, the transaction is still running. This is
|
|
* therefore faster than HeapTupleSatisfiesVacuum, because we don't
|
|
* consult CLOG (and also because we don't need to give an exact answer,
|
|
* just whether or not the tuple is surely dead).
|
|
*/
|
|
bool HeapTupleIsSurelyDead(HeapTuple tuple, TransactionId OldestXmin)
|
|
{
|
|
HeapTupleHeader tup = tuple->t_data;
|
|
|
|
/*
|
|
* If the inserting transaction is marked invalid, then it aborted, and
|
|
* the tuple is definitely dead. If it's marked neither committed nor
|
|
* invalid, then we assume it's still alive (since the presumption is that
|
|
* all relevant hint bits were just set moments ago).
|
|
*/
|
|
if (!HeapTupleHeaderXminCommitted(tup))
|
|
return HeapTupleHeaderXminInvalid(tup) ? true : false;
|
|
|
|
/*
|
|
* If the inserting transaction committed, but any deleting transaction
|
|
* aborted, the tuple is still alive. Likewise, if XMAX is a lock rather
|
|
* than a delete, the tuple is still alive.
|
|
*/
|
|
if (tup->t_infomask & (HEAP_XMAX_INVALID | HEAP_IS_LOCKED | HEAP_XMAX_IS_MULTI))
|
|
return false;
|
|
|
|
/* If deleter isn't known to have committed, assume it's still running. */
|
|
if (!(tup->t_infomask & HEAP_XMAX_COMMITTED))
|
|
return false;
|
|
|
|
/* Deleter committed, so tuple is dead if the XID is old enough. */
|
|
return TransactionIdPrecedes(HeapTupleGetRawXmax(tuple), OldestXmin);
|
|
}
|
|
|
|
/*
|
|
* XidVisibleInSnapshot
|
|
* Is the given XID visible according to the snapshot?
|
|
*
|
|
* On return, *hintstatus is set to indicate if the transaction had committed,
|
|
* or aborted, whether or not it's not visible to us.
|
|
*/
|
|
static bool XidVisibleInSnapshot(TransactionId xid, Snapshot snapshot, TransactionIdStatus* hintstatus)
|
|
{
|
|
volatile CommitSeqNo csn;
|
|
bool looped = false;
|
|
TransactionId parentXid = InvalidTransactionId;
|
|
|
|
*hintstatus = XID_INPROGRESS;
|
|
|
|
ereport(DEBUG1,
|
|
(errmsg("XidVisibleInSnapshot xid " XID_FMT " cur_xid " XID_FMT " snapshot csn " CSN_FMT " xmax " XID_FMT,
|
|
xid,
|
|
GetCurrentTransactionIdIfAny(),
|
|
snapshot->snapshotcsn,
|
|
snapshot->xmax)));
|
|
|
|
/*
|
|
* Any xid >= xmax is in-progress (or aborted, but we don't distinguish
|
|
* that here).
|
|
*
|
|
* We can't do anything useful with xmin, because the xmin only tells us
|
|
* whether we see it as completed. We have to check the transaction log to
|
|
* see if the transaction committed or aborted, in any case.
|
|
*/
|
|
if (!GTM_LITE_MODE || snapshot->snapshot_type == SNAPSHOT_TYPE_LOCAL) {
|
|
if (TransactionIdFollowsOrEquals(xid, snapshot->xmax))
|
|
return false;
|
|
}
|
|
|
|
loop:
|
|
csn = TransactionIdGetCommitSeqNo(xid, false, true, false);
|
|
|
|
ereport(DEBUG1,
|
|
(errmsg("XidVisibleInSnapshot xid " XID_FMT " cur_xid " XID_FMT " csn " CSN_FMT " snapshot"
|
|
" csn " CSN_FMT " xmax " XID_FMT,
|
|
xid,
|
|
GetCurrentTransactionIdIfAny(),
|
|
csn,
|
|
snapshot->snapshotcsn,
|
|
snapshot->xmax)));
|
|
|
|
if (COMMITSEQNO_IS_COMMITTED(csn)) {
|
|
*hintstatus = XID_COMMITTED;
|
|
if (csn < snapshot->snapshotcsn)
|
|
return true;
|
|
else
|
|
return false;
|
|
} else if (COMMITSEQNO_IS_COMMITTING(csn)) {
|
|
if (looped) {
|
|
ereport(DEBUG1, (errmsg("transaction id " XID_FMT "'s csn " CSN_FMT " is changed to ABORT after lockwait.", xid, csn)));
|
|
CSNLogSetCommitSeqNo(xid, 0, NULL, COMMITSEQNO_ABORTED);
|
|
SetLatestFetchState(xid, COMMITSEQNO_ABORTED);
|
|
*hintstatus = XID_ABORTED;
|
|
return false;
|
|
} else {
|
|
if (!COMMITSEQNO_IS_SUBTRANS(csn)) {
|
|
/* If snapshotcsn lower than csn stored in csn log, don't need to wait. */
|
|
CommitSeqNo latestCSN = GET_COMMITSEQNO(csn);
|
|
if (latestCSN >= snapshot->snapshotcsn) {
|
|
ereport(DEBUG1,
|
|
(errmsg(
|
|
"snapshotcsn " XID_FMT " lower than csn " CSN_FMT " stored in csn log, don't need to sync wait, trx id " XID_FMT,
|
|
snapshot->snapshotcsn,
|
|
csn,
|
|
xid)));
|
|
return false;
|
|
}
|
|
} else {
|
|
parentXid = (TransactionId)GET_PARENTXID(csn);
|
|
}
|
|
|
|
if (u_sess->attr.attr_common.xc_maintenance_mode) {
|
|
return false;
|
|
}
|
|
|
|
/* Wait for txn end and check again. */
|
|
if (TransactionIdIsValid(parentXid))
|
|
SyncLocalXidWait(parentXid);
|
|
else
|
|
SyncLocalXidWait(xid);
|
|
looped = true;
|
|
parentXid = InvalidTransactionId;
|
|
goto loop;
|
|
}
|
|
} else {
|
|
if (csn == COMMITSEQNO_ABORTED)
|
|
*hintstatus = XID_ABORTED;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* XidVisibleInLocalSnapshot
|
|
* Is the given XID visible according to the local multi-version snapshot?
|
|
*
|
|
* On return whether or not it's not visible to us.
|
|
*/
|
|
static bool XidVisibleInLocalSnapshot(TransactionId xid, Snapshot snapshot)
|
|
{
|
|
volatile CommitSeqNo csn;
|
|
bool looped = false;
|
|
TransactionId parentXid = InvalidTransactionId;
|
|
|
|
ereport(DEBUG1,
|
|
(errmsg("XidVisibleInSnapshot xid " XID_FMT " cur_xid " XID_FMT " snapshot csn " CSN_FMT " xmax " XID_FMT,
|
|
xid,
|
|
GetCurrentTransactionIdIfAny(),
|
|
snapshot->snapshotcsn,
|
|
snapshot->xmax)));
|
|
|
|
/*
|
|
* Any xid >= xmax is in-progress (or aborted, but we don't distinguish
|
|
* that here).
|
|
*
|
|
* We can't do anything useful with xmin, because the xmin only tells us
|
|
* whether we see it as completed. We have to check the transaction log to
|
|
* see if the transaction committed or aborted, in any case.
|
|
*/
|
|
if (!GTM_LITE_MODE || snapshot->snapshot_type == SNAPSHOT_TYPE_LOCAL) {
|
|
if (TransactionIdFollowsOrEquals(xid, snapshot->xmax))
|
|
return false;
|
|
}
|
|
|
|
loop:
|
|
csn = TransactionIdGetCommitSeqNoNCache(xid, false, true, false);
|
|
|
|
ereport(DEBUG1,
|
|
(errmsg("XidVisibleInSnapshot xid " XID_FMT " cur_xid " XID_FMT " csn " CSN_FMT " snapshot"
|
|
"csn " CSN_FMT " xmax " XID_FMT,
|
|
xid,
|
|
GetCurrentTransactionIdIfAny(),
|
|
csn,
|
|
snapshot->snapshotcsn,
|
|
snapshot->xmax)));
|
|
|
|
if (COMMITSEQNO_IS_COMMITTED(csn)) {
|
|
if (csn < snapshot->snapshotcsn)
|
|
return true;
|
|
else
|
|
return false;
|
|
} else if (COMMITSEQNO_IS_COMMITTING(csn)) {
|
|
if (looped) {
|
|
ereport(DEBUG1, (errmsg("transaction id " XID_FMT "'s csn " CSN_FMT " is changed to ABORT after lockwait.", xid, csn)));
|
|
return false;
|
|
} else {
|
|
if (COMMITSEQNO_IS_SUBTRANS(csn)) {
|
|
parentXid = (TransactionId)GET_PARENTXID(csn);
|
|
}
|
|
|
|
if (u_sess->attr.attr_common.xc_maintenance_mode) {
|
|
return false;
|
|
}
|
|
|
|
/* Wait for txn end and check again. */
|
|
if (TransactionIdIsValid(parentXid))
|
|
SyncLocalXidWait(parentXid);
|
|
else
|
|
SyncLocalXidWait(xid);
|
|
looped = true;
|
|
parentXid = InvalidTransactionId;
|
|
goto loop;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CommittedXidVisibleInSnapshot
|
|
* Is the given XID visible according to the snapshot?
|
|
*
|
|
* This is the same as XidVisibleInSnapshot, but the caller knows that the
|
|
* given XID committed. The only question is whether it's visible to our
|
|
* snapshot or not.
|
|
*/
|
|
static bool CommittedXidVisibleInSnapshot(TransactionId xid, Snapshot snapshot)
|
|
{
|
|
CommitSeqNo csn;
|
|
bool looped = false;
|
|
TransactionId parentXid = InvalidTransactionId;
|
|
|
|
if (!GTM_LITE_MODE || snapshot->snapshot_type == SNAPSHOT_TYPE_LOCAL) {
|
|
/*
|
|
* Make a quick range check to eliminate most XIDs without looking at the
|
|
* CSN log.
|
|
*/
|
|
if (TransactionIdPrecedes(xid, snapshot->xmin))
|
|
return true;
|
|
|
|
/*
|
|
* Any xid >= xmax is in-progress (or aborted, but we don't distinguish
|
|
* that here.
|
|
*/
|
|
if (TransactionIdFollowsOrEquals(xid, snapshot->xmax))
|
|
return false;
|
|
}
|
|
|
|
loop:
|
|
csn = TransactionIdGetCommitSeqNo(xid, true, true, false);
|
|
|
|
if (COMMITSEQNO_IS_COMMITTING(csn)) {
|
|
if (looped) {
|
|
ereport(WARNING,
|
|
(errmsg("committed transaction id " XID_FMT "'s csn " CSN_FMT
|
|
"is changed to frozen after lockwait.",
|
|
xid,
|
|
csn)));
|
|
CSNLogSetCommitSeqNo(xid, 0, NULL, COMMITSEQNO_FROZEN);
|
|
SetLatestFetchState(xid, COMMITSEQNO_FROZEN);
|
|
return true;
|
|
} else {
|
|
if (!COMMITSEQNO_IS_SUBTRANS(csn)) {
|
|
/* If snapshotcsn lower than csn stored in csn log, don't need to wait. */
|
|
CommitSeqNo latestCSN = GET_COMMITSEQNO(csn);
|
|
if (latestCSN >= snapshot->snapshotcsn) {
|
|
ereport(DEBUG1,
|
|
(errmsg("snapshotcsn " XID_FMT " lower than csn " CSN_FMT
|
|
" stored in csn log, don't need to sync wait, trx id " XID_FMT,
|
|
snapshot->snapshotcsn,
|
|
csn,
|
|
xid)));
|
|
return false;
|
|
}
|
|
} else {
|
|
parentXid = (TransactionId)GET_PARENTXID(csn);
|
|
}
|
|
|
|
if (u_sess->attr.attr_common.xc_maintenance_mode) {
|
|
return false;
|
|
}
|
|
|
|
/* Wait for txn end and check again. */
|
|
if (TransactionIdIsValid(parentXid))
|
|
SyncLocalXidWait(parentXid);
|
|
else
|
|
SyncLocalXidWait(xid);
|
|
looped = true;
|
|
parentXid = InvalidTransactionId;
|
|
goto loop;
|
|
}
|
|
} else if (!COMMITSEQNO_IS_COMMITTED(csn)) {
|
|
ereport(WARNING,
|
|
(errmsg("transaction/csn " XID_FMT "/" CSN_FMT " was hinted as "
|
|
"committed, but was not marked as committed in "
|
|
"the transaction log",
|
|
xid,
|
|
csn)));
|
|
/*
|
|
* We have contradicting evidence on whether the transaction committed or
|
|
* not. Let's assume that it did. That seems better than erroring out.
|
|
*/
|
|
return true;
|
|
}
|
|
|
|
if (csn < snapshot->snapshotcsn)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* check whether the transaciont id 'xid' in in the pre-sorted array 'xip'.
|
|
*/
|
|
static bool TransactionIdInArray(TransactionId xid, TransactionId* xip, Size num)
|
|
{
|
|
return bsearch(&xid, xip, num, sizeof(TransactionId), xidComparator) != NULL;
|
|
}
|
|
|
|
/*
|
|
* See the comments for HeapTupleSatisfiesMVCC for the semantics this function
|
|
* obeys.
|
|
*
|
|
* Only usable on tuples from catalog tables!
|
|
*
|
|
* We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
|
|
* reading catalog pages which couldn't have been created in an older version.
|
|
*
|
|
* We don't set any hint bits in here as it seems unlikely to be beneficial as
|
|
* those should already be set by normal access and it seems to be too
|
|
* dangerous to do so as the semantics of doing so during timetravel are more
|
|
* complicated than when dealing "only" with the present.
|
|
*/
|
|
bool HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot, Buffer buffer)
|
|
{
|
|
Page page = BufferGetPage(buffer);
|
|
HeapTupleHeader tuple = htup->t_data;
|
|
TransactionId xmin = HeapTupleHeaderGetXmin(page, tuple);
|
|
TransactionId xmax = HeapTupleHeaderGetRawXmax(page, tuple);
|
|
|
|
Assert(ItemPointerIsValid(&htup->t_self));
|
|
Assert(htup->t_tableOid != InvalidOid);
|
|
|
|
/* inserting transaction aborted */
|
|
if (HeapTupleHeaderXminInvalid(tuple)) {
|
|
Assert(!TransactionIdDidCommit(xmin));
|
|
return false;
|
|
}
|
|
/* check if its one of our txids, toplevel is also in there */
|
|
else if (TransactionIdInArray(xmin, snapshot->subxip, snapshot->subxcnt)) {
|
|
bool resolved = false;
|
|
CommandId cmin = HeapTupleHeaderGetRawCommandId(tuple);
|
|
CommandId cmax = InvalidCommandId;
|
|
|
|
/*
|
|
* another transaction might have (tried to) delete this tuple or
|
|
* cmin/cmax was stored in a combocid. S we need to to lookup the
|
|
* actual values externally.
|
|
*/
|
|
resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot, htup, buffer, &cmin, &cmax);
|
|
|
|
if (!resolved)
|
|
ereport(ERROR, (errcode(ERRCODE_NO_DATA_FOUND), errmsg("could not resolve cmin/cmax of catalog tuple")));
|
|
|
|
Assert(cmin != InvalidCommandId);
|
|
|
|
if (cmin >= snapshot->curcid)
|
|
return false; /* inserted after scan started */
|
|
/* fall through */
|
|
}
|
|
/* committed before our xmin horizon. Do a normal visibility check. */
|
|
else if (TransactionIdPrecedes(xmin, snapshot->xmin)) {
|
|
Assert(!(HeapTupleHeaderXminCommitted(tuple) && !TransactionIdDidCommit(xmin)));
|
|
|
|
/* check for hint bit first, consult clog afterwards */
|
|
if (!HeapTupleHeaderXminCommitted(tuple) && !TransactionIdDidCommit(xmin))
|
|
return false;
|
|
/* fall through */
|
|
}
|
|
/* beyond our xmax horizon, i.e. invisible */
|
|
else if (TransactionIdFollowsOrEquals(xmin, snapshot->xmax)) {
|
|
return false;
|
|
}
|
|
/* check if it's a committed transaction in [xmin, xmax) */
|
|
else if (TransactionIdInArray(xmin, snapshot->xip, snapshot->xcnt)) {
|
|
/* fall through */
|
|
}
|
|
/*
|
|
* none of the above, i.e. between [xmin, xmax) but hasn't
|
|
* committed. I.e. invisible.
|
|
*/
|
|
else {
|
|
return false;
|
|
}
|
|
|
|
/* at this point we know xmin is visible, go on to check xmax */
|
|
|
|
/* xid invalid or aborted */
|
|
if (tuple->t_infomask & HEAP_XMAX_INVALID)
|
|
return true;
|
|
|
|
/*
|
|
* The content had been updated in patch
|
|
* We keep the old style here.
|
|
*/
|
|
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
|
|
|
|
/* check if its one of our txids, toplevel is also in there */
|
|
if (TransactionIdInArray(xmax, snapshot->subxip, snapshot->subxcnt)) {
|
|
bool resolved = false;
|
|
CommandId cmin;
|
|
CommandId cmax = HeapTupleHeaderGetRawCommandId(tuple);
|
|
|
|
/* Lookup actual cmin/cmax values */
|
|
resolved = ResolveCminCmaxDuringDecoding(HistoricSnapshotGetTupleCids(), snapshot, htup, buffer, &cmin, &cmax);
|
|
|
|
if (!resolved)
|
|
ereport(ERROR, (errcode(ERRCODE_NO_DATA_FOUND), errmsg("could not resolve combocid to cmax")));
|
|
|
|
Assert(cmax != InvalidCommandId);
|
|
|
|
if (cmax >= snapshot->curcid)
|
|
return true; /* deleted after scan started */
|
|
else
|
|
return false; /* deleted before scan started */
|
|
}
|
|
/* below xmin horizon, normal transaction state is valid */
|
|
else if (TransactionIdPrecedes(xmax, snapshot->xmin)) {
|
|
Assert(!((tuple->t_infomask & HEAP_XMAX_COMMITTED) && (!TransactionIdDidCommit(xmax))));
|
|
|
|
/* check hint bit first */
|
|
if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
|
|
return false;
|
|
|
|
/* check clog */
|
|
return !TransactionIdDidCommit(xmax);
|
|
}
|
|
/* above xmax horizon, we cannot possibly see the deleting transaction */
|
|
else if (TransactionIdFollowsOrEquals(xmax, snapshot->xmax))
|
|
return true;
|
|
/* xmax is between [xmin, xmax), check known committed array */
|
|
else if (TransactionIdInArray(xmax, snapshot->xip, snapshot->xcnt))
|
|
return false;
|
|
/* xmax is between [xmin, xmax), but known not to have committed yet */
|
|
else
|
|
return true;
|
|
return true;
|
|
}
|