/*------------------------------------------------------------------------- * * transam.c * postgres transaction (commit) log interface routines * * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/access/transam/transam.c * * NOTES * This file contains the high level access-method interface to the * transaction system. * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/clog.h" #include "access/subtrans.h" #include "access/transam.h" #include "utils/snapmgr.h" /* * Single-item cache for results of TransactionLogFetch. It's worth having * such a cache because we frequently find ourselves repeatedly checking the * same XID, for example when scanning a table just after a bulk insert, * update, or delete. */ static TransactionId cachedFetchXid = InvalidTransactionId; static XidStatus cachedFetchXidStatus; static XLogRecPtr cachedCommitLSN; /* Local functions */ static XidStatus TransactionLogFetch(TransactionId transactionId); /* ---------------------------------------------------------------- * Postgres log access method interface * * TransactionLogFetch * ---------------------------------------------------------------- */ /* * TransactionLogFetch --- fetch commit status of specified transaction id */ static XidStatus TransactionLogFetch(TransactionId transactionId) { XidStatus xidstatus; XLogRecPtr xidlsn; /* * Before going to the commit log manager, check our single item cache to * see if we didn't just check the transaction status a moment ago. */ if (TransactionIdEquals(transactionId, cachedFetchXid)) return cachedFetchXidStatus; /* * Also, check to see if the transaction ID is a permanent one. */ if (!TransactionIdIsNormal(transactionId)) { if (TransactionIdEquals(transactionId, BootstrapTransactionId)) return TRANSACTION_STATUS_COMMITTED; if (TransactionIdEquals(transactionId, FrozenTransactionId)) return TRANSACTION_STATUS_COMMITTED; return TRANSACTION_STATUS_ABORTED; } /* * Get the transaction status. */ xidstatus = TransactionIdGetStatus(transactionId, &xidlsn); /* * Cache it, but DO NOT cache status for unfinished or sub-committed * transactions! We only cache status that is guaranteed not to change. */ if (xidstatus != TRANSACTION_STATUS_IN_PROGRESS && xidstatus != TRANSACTION_STATUS_SUB_COMMITTED) { cachedFetchXid = transactionId; cachedFetchXidStatus = xidstatus; cachedCommitLSN = xidlsn; } return xidstatus; } /* ---------------------------------------------------------------- * Interface functions * * TransactionIdDidCommit * TransactionIdDidAbort * ======== * these functions test the transaction status of * a specified transaction id. * * TransactionIdCommitTree * TransactionIdAsyncCommitTree * TransactionIdAbortTree * ======== * these functions set the transaction status of the specified * transaction tree. * * See also TransactionIdIsInProgress, which once was in this module * but now lives in procarray.c, as well as comments at the top of * heapam_visibility.c that explain how everything fits together. * ---------------------------------------------------------------- */ /* * TransactionIdDidCommit * True iff transaction associated with the identifier did commit. * * Note: * Assumes transaction identifier is valid and exists in clog. */ bool /* true if given transaction committed */ TransactionIdDidCommit(TransactionId transactionId) { XidStatus xidstatus; xidstatus = TransactionLogFetch(transactionId); /* * If it's marked committed, it's committed. */ if (xidstatus == TRANSACTION_STATUS_COMMITTED) return true; /* * If it's marked subcommitted, we have to check the parent recursively. * However, if it's older than TransactionXmin, we can't look at * pg_subtrans; instead assume that the parent crashed without cleaning up * its children. * * Originally we Assert'ed that the result of SubTransGetParent was not * zero. However with the introduction of prepared transactions, there can * be a window just after database startup where we do not have complete * knowledge in pg_subtrans of the transactions after TransactionXmin. * StartupSUBTRANS() has ensured that any missing information will be * zeroed. Since this case should not happen under normal conditions, it * seems reasonable to emit a WARNING for it. */ if (xidstatus == TRANSACTION_STATUS_SUB_COMMITTED) { TransactionId parentXid; if (TransactionIdPrecedes(transactionId, TransactionXmin)) return false; parentXid = SubTransGetParent(transactionId); if (!TransactionIdIsValid(parentXid)) { elog(WARNING, "no pg_subtrans entry for subcommitted XID %u", transactionId); return false; } return TransactionIdDidCommit(parentXid); } /* * It's not committed. */ return false; } /* * TransactionIdDidAbort * True iff transaction associated with the identifier did abort. * * Note: * Assumes transaction identifier is valid and exists in clog. * * Returns true only for explicitly aborted transactions, as transactions * implicitly aborted due to a crash will commonly still appear to be * in-progress in the clog. Most of the time TransactionIdDidCommit(), * with a preceding TransactionIdIsInProgress() check, should be used * instead of TransactionIdDidAbort(). */ bool /* true if given transaction aborted */ TransactionIdDidAbort(TransactionId transactionId) { XidStatus xidstatus; xidstatus = TransactionLogFetch(transactionId); /* * If it's marked aborted, it's aborted. */ if (xidstatus == TRANSACTION_STATUS_ABORTED) return true; /* * If it's marked subcommitted, we have to check the parent recursively. * However, if it's older than TransactionXmin, we can't look at * pg_subtrans; instead assume that the parent crashed without cleaning up * its children. */ if (xidstatus == TRANSACTION_STATUS_SUB_COMMITTED) { TransactionId parentXid; if (TransactionIdPrecedes(transactionId, TransactionXmin)) return true; parentXid = SubTransGetParent(transactionId); if (!TransactionIdIsValid(parentXid)) { /* see notes in TransactionIdDidCommit */ elog(WARNING, "no pg_subtrans entry for subcommitted XID %u", transactionId); return true; } return TransactionIdDidAbort(parentXid); } /* * It's not aborted. */ return false; } /* * TransactionIdCommitTree * Marks the given transaction and children as committed * * "xid" is a toplevel transaction commit, and the xids array contains its * committed subtransactions. * * This commit operation is not guaranteed to be atomic, but if not, subxids * are correctly marked subcommit first. */ void TransactionIdCommitTree(TransactionId xid, int nxids, TransactionId *xids) { TransactionIdSetTreeStatus(xid, nxids, xids, TRANSACTION_STATUS_COMMITTED, InvalidXLogRecPtr); } /* * TransactionIdAsyncCommitTree * Same as above, but for async commits. The commit record LSN is needed. */ void TransactionIdAsyncCommitTree(TransactionId xid, int nxids, TransactionId *xids, XLogRecPtr lsn) { TransactionIdSetTreeStatus(xid, nxids, xids, TRANSACTION_STATUS_COMMITTED, lsn); } /* * TransactionIdAbortTree * Marks the given transaction and children as aborted. * * "xid" is a toplevel transaction commit, and the xids array contains its * committed subtransactions. * * We don't need to worry about the non-atomic behavior, since any onlookers * will consider all the xacts as not-yet-committed anyway. */ void TransactionIdAbortTree(TransactionId xid, int nxids, TransactionId *xids) { TransactionIdSetTreeStatus(xid, nxids, xids, TRANSACTION_STATUS_ABORTED, InvalidXLogRecPtr); } /* * TransactionIdPrecedes --- is id1 logically < id2? */ bool TransactionIdPrecedes(TransactionId id1, TransactionId id2) { /* * If either ID is a permanent XID then we can just do unsigned * comparison. If both are normal, do a modulo-2^32 comparison. */ int32 diff; if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2)) return (id1 < id2); diff = (int32) (id1 - id2); return (diff < 0); } /* * TransactionIdPrecedesOrEquals --- is id1 logically <= id2? */ bool TransactionIdPrecedesOrEquals(TransactionId id1, TransactionId id2) { int32 diff; if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2)) return (id1 <= id2); diff = (int32) (id1 - id2); return (diff <= 0); } /* * TransactionIdFollows --- is id1 logically > id2? */ bool TransactionIdFollows(TransactionId id1, TransactionId id2) { int32 diff; if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2)) return (id1 > id2); diff = (int32) (id1 - id2); return (diff > 0); } /* * TransactionIdFollowsOrEquals --- is id1 logically >= id2? */ bool TransactionIdFollowsOrEquals(TransactionId id1, TransactionId id2) { int32 diff; if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2)) return (id1 >= id2); diff = (int32) (id1 - id2); return (diff >= 0); } /* * TransactionIdLatest --- get latest XID among a main xact and its children */ TransactionId TransactionIdLatest(TransactionId mainxid, int nxids, const TransactionId *xids) { TransactionId result; /* * In practice it is highly likely that the xids[] array is sorted, and so * we could save some cycles by just taking the last child XID, but this * probably isn't so performance-critical that it's worth depending on * that assumption. But just to show we're not totally stupid, scan the * array back-to-front to avoid useless assignments. */ result = mainxid; while (--nxids >= 0) { if (TransactionIdPrecedes(result, xids[nxids])) result = xids[nxids]; } return result; } /* * TransactionIdGetCommitLSN * * This function returns an LSN that is late enough to be able * to guarantee that if we flush up to the LSN returned then we * will have flushed the transaction's commit record to disk. * * The result is not necessarily the exact LSN of the transaction's * commit record! For example, for long-past transactions (those whose * clog pages already migrated to disk), we'll return InvalidXLogRecPtr. * Also, because we group transactions on the same clog page to conserve * storage, we might return the LSN of a later transaction that falls into * the same group. */ XLogRecPtr TransactionIdGetCommitLSN(TransactionId xid) { XLogRecPtr result; /* * Currently, all uses of this function are for xids that were just * reported to be committed by TransactionLogFetch, so we expect that * checking TransactionLogFetch's cache will usually succeed and avoid an * extra trip to shared memory. */ if (TransactionIdEquals(xid, cachedFetchXid)) return cachedCommitLSN; /* Special XIDs are always known committed */ if (!TransactionIdIsNormal(xid)) return InvalidXLogRecPtr; /* * Get the transaction status. */ (void) TransactionIdGetStatus(xid, &result); return result; }