diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c index 3d9088a704..d690774f33 100644 --- a/src/backend/access/transam/clog.c +++ b/src/backend/access/transam/clog.c @@ -107,7 +107,18 @@ static bool TransactionGroupUpdateXidStatus(TransactionId xid, static void TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids, TransactionId *subxids, XidStatus status, XLogRecPtr lsn, int pageno); +/* + * Run locally by a backend to establish whether or not it needs to call + * SubTransSetParent for subxid. + */ +bool +TransactionIdsAreOnSameXactPage(TransactionId topxid, TransactionId subxid) +{ + int toppageno = TransactionIdToPage(topxid); + int subpageno = TransactionIdToPage(subxid); + return (toppageno == subpageno); +} /* * TransactionIdSetTreeStatus @@ -133,7 +144,7 @@ static void TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids, * only once, and the status will be set to committed directly. Otherwise * we must * 1. set sub-committed all subxids that are not on the same page as the - * main xid + * main xid (see TransactionIdsAreOnSameXactPage()) * 2. atomically set committed the main xid and the subxids on the same page * 3. go over the first bunch again and set them committed * Note that as far as concurrent checkers are concerned, main transaction diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 50f092d7eb..5577e5b8df 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -693,8 +693,51 @@ AssignTransactionId(TransactionState s) XactTopFullTransactionId = s->fullTransactionId; if (isSubXact) - SubTransSetParent(XidFromFullTransactionId(s->fullTransactionId), - XidFromFullTransactionId(s->parent->fullTransactionId)); + { + TransactionId subxid = XidFromFullTransactionId(s->fullTransactionId); + + /* + * Subtrans entries are only required in specific circumstances: + * + * 1. When there's no room in PG_PROC, as mentioned above. + * During XactLockTableWait() we sometimes need to know the topxid. + * If there is room in PG_PROC we can get a subxid's topxid direct + * from the procarray if the topxid is still running, using + * GetTopmostTransactionIdFromProcArray(). So we only ever need to + * call SubTransGetTopMostTransaction() if that xact overflowed; + * since that is our current transaction, we know whether or not to + * log the xid for future use. + * This occurs only when large number of subxids are requested by + * app user. + * + * 2. When IsolationIsSerializable() we sometimes need to access topxid + * This occurs only when SERIALIZABLE is requested by app user. + * + * 3. When TransactionIdSetStatus will use a status of SUB_COMMITTED, + * which then requires us to consult subtrans to find parent, which + * is needed to avoid race condition. In this case we ask Clog/Xact + * module if TransactionIdsAreOnSameXactPage(). Since we start a new + * clog page every 32000 xids, this is usually <<1% of subxids. + */ + if (MyProc->subxidStatus.overflowed || + IsolationIsSerializable() || + !TransactionIdsAreOnSameXactPage(GetTopTransactionId(), subxid)) + { + /* + * Insert entries into subtrans for this xid, noting that the entry + * points directly to the topxid, not the immediate parent. This is + * done for two reasons, (1) so it is faster in a long chain of subxids + * (2) so that we don't need to set subxids for unregistered parents. + * This has the downside that anyone waiting for a lock on aborted + * subtransactions would not be released immediately; that may or + * may not be an acceptable compromise. If not acceptable, this + * simple call needs to be replaced with a loop to register the + * parent for the current subxid stack, so we can walk back up it to + * the topxid. + */ + SubTransSetParent(subxid, GetTopTransactionId()); + } + } /* * If it's a top-level transaction, the predicate locking system needs to diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index a9ad40e935..f7c1061a6e 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -261,6 +261,13 @@ static ProcArrayStruct *procArray; static PGPROC *allProcs; +/* + * Remember the last call to TransactionIdIsInProgress() to avoid need to call + * SubTransGetTopMostTransaction() when the subxid is present in the procarray. + */ +static TransactionId LastCallXidIsInProgressSubXid = InvalidTransactionId; +static TransactionId LastCallXidIsInProgressParentXid = InvalidTransactionId; + /* * Cache to reduce overhead of repeated calls to TransactionIdIsInProgress() */ @@ -1440,6 +1447,8 @@ TransactionIdIsInProgress(TransactionId xid) other_xids = ProcGlobal->xids; other_subxidstates = ProcGlobal->subxidStates; + LastCallXidIsInProgressSubXid = LastCallXidIsInProgressParentXid = InvalidTransactionId; + LWLockAcquire(ProcArrayLock, LW_SHARED); /* @@ -1508,6 +1517,15 @@ TransactionIdIsInProgress(TransactionId xid) { LWLockRelease(ProcArrayLock); xc_by_child_xid_inc(); + + /* + * Remember the parent xid, for use during XactLockTableWait(). + * We do this because it is cheaper than looking up pg_subtrans, + * and also allows us to reduce calls to subtrans. + */ + LastCallXidIsInProgressSubXid = xid; + LastCallXidIsInProgressParentXid = pxid; + return true; } } @@ -1591,7 +1609,11 @@ TransactionIdIsInProgress(TransactionId xid) for (int i = 0; i < nxids; i++) { if (TransactionIdEquals(xids[i], topxid)) + { + LastCallXidIsInProgressSubXid = xid; + LastCallXidIsInProgressParentXid = topxid; return true; + } } } @@ -1599,6 +1621,28 @@ TransactionIdIsInProgress(TransactionId xid) return false; } +/* + * Allow the topmost xid to be accessed from the last call to + * TransactionIdIsInProgress(). Specifically designed for use in + * XactLockTableWait(). + */ +bool +GetTopmostTransactionIdFromProcArray(TransactionId xid, TransactionId *pxid) +{ + bool found = false; + + Assert(TransactionIdIsNormal(xid)); + + if (LastCallXidIsInProgressSubXid == xid) + { + Assert(TransactionIdIsNormal(*pxid)); + *pxid = LastCallXidIsInProgressParentXid; + found = true; + } + + return found; +} + /* * TransactionIdIsActive -- is xid the top-level XID of an active backend? * diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c index 1543da6162..b13a8d20fd 100644 --- a/src/backend/storage/lmgr/lmgr.c +++ b/src/backend/storage/lmgr/lmgr.c @@ -694,6 +694,8 @@ XactLockTableWait(TransactionId xid, Relation rel, ItemPointer ctid, for (;;) { + TransactionId pxid = InvalidTransactionId; + Assert(TransactionIdIsValid(xid)); Assert(!TransactionIdEquals(xid, GetTopTransactionIdIfAny())); @@ -703,6 +705,13 @@ XactLockTableWait(TransactionId xid, Relation rel, ItemPointer ctid, LockRelease(&tag, ShareLock, false); + /* + * If a transaction has no lock, it might be a top-level transaction, + * in which case the procarray will show it as not in progress. + * + * If a transaction is a subtransaction, then it could have committed + * or aborted, yet the top-level transaction may still be in progress. + */ if (!TransactionIdIsInProgress(xid)) break; @@ -724,7 +733,17 @@ XactLockTableWait(TransactionId xid, Relation rel, ItemPointer ctid, if (!first) pg_usleep(1000L); first = false; - xid = SubTransGetTopmostTransaction(xid); + + /* + * In most cases, we can get the parent xid from our prior call to + * TransactionIdIsInProgress(), except in hot standby. If not, we have + * to ask subtrans for the parent. + */ + if (GetTopmostTransactionIdFromProcArray(xid, &pxid) && + TransactionIdIsValid(pxid)) + xid = pxid; + else + xid = SubTransGetTopmostTransaction(xid); } if (oper != XLTW_None) @@ -745,6 +764,8 @@ ConditionalXactLockTableWait(TransactionId xid) for (;;) { + TransactionId pxid = InvalidTransactionId; + Assert(TransactionIdIsValid(xid)); Assert(!TransactionIdEquals(xid, GetTopTransactionIdIfAny())); @@ -762,7 +783,13 @@ ConditionalXactLockTableWait(TransactionId xid) if (!first) pg_usleep(1000L); first = false; - xid = SubTransGetTopmostTransaction(xid); + + /* See XactLockTableWait about this case */ + if (GetTopmostTransactionIdFromProcArray(xid, &pxid) && + TransactionIdIsValid(pxid)) + xid = pxid; + else + xid = SubTransGetTopmostTransaction(xid); } return true; diff --git a/src/include/access/transam.h b/src/include/access/transam.h index 775471d2a7..f404db552f 100644 --- a/src/include/access/transam.h +++ b/src/include/access/transam.h @@ -301,6 +301,9 @@ extern void AssertTransactionIdInAllowableRange(TransactionId xid); #define AssertTransactionIdInAllowableRange(xid) ((void)true) #endif +/* in transam/clog.c */ +extern bool TransactionIdsAreOnSameXactPage(TransactionId topxid, TransactionId subxid); + /* * Some frontend programs include this header. For compilers that emit static * inline functions even when they're unused, that leads to unsatisfied diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h index 1b2cfac5ad..d7ad1da6a8 100644 --- a/src/include/storage/procarray.h +++ b/src/include/storage/procarray.h @@ -52,6 +52,8 @@ extern bool ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc); extern RunningTransactions GetRunningTransactionData(void); extern bool TransactionIdIsInProgress(TransactionId xid); +extern bool GetTopmostTransactionIdFromProcArray(TransactionId xid, TransactionId *pxid); + extern bool TransactionIdIsActive(TransactionId xid); extern TransactionId GetOldestNonRemovableTransactionId(Relation rel); extern TransactionId GetOldestTransactionIdConsideredRunning(void);