Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * transam.c
4 : : * postgres transaction (commit) log interface routines
5 : : *
6 : : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/access/transam/transam.c
12 : : *
13 : : * NOTES
14 : : * This file contains the high level access-method interface to the
15 : : * transaction system.
16 : : *
17 : : *-------------------------------------------------------------------------
18 : : */
19 : :
20 : : #include "postgres.h"
21 : :
22 : : #include "access/clog.h"
23 : : #include "access/subtrans.h"
24 : : #include "access/transam.h"
25 : : #include "utils/snapmgr.h"
26 : :
27 : : /*
28 : : * Single-item cache for results of TransactionLogFetch. It's worth having
29 : : * such a cache because we frequently find ourselves repeatedly checking the
30 : : * same XID, for example when scanning a table just after a bulk insert,
31 : : * update, or delete.
32 : : */
33 : : static TransactionId cachedFetchXid = InvalidTransactionId;
34 : : static XidStatus cachedFetchXidStatus;
35 : : static XLogRecPtr cachedCommitLSN;
36 : :
37 : : /* Local functions */
38 : : static XidStatus TransactionLogFetch(TransactionId transactionId);
39 : :
40 : :
41 : : /* ----------------------------------------------------------------
42 : : * Postgres log access method interface
43 : : *
44 : : * TransactionLogFetch
45 : : * ----------------------------------------------------------------
46 : : */
47 : :
48 : : /*
49 : : * TransactionLogFetch --- fetch commit status of specified transaction id
50 : : */
51 : : static XidStatus
7789 tgl@sss.pgh.pa.us 52 :CBC 10672069 : TransactionLogFetch(TransactionId transactionId)
53 : : {
54 : : XidStatus xidstatus;
55 : : XLogRecPtr xidlsn;
56 : :
57 : : /*
58 : : * Before going to the commit log manager, check our single item cache to
59 : : * see if we didn't just check the transaction status a moment ago.
60 : : */
61 [ + + ]: 10672069 : if (TransactionIdEquals(transactionId, cachedFetchXid))
62 : 9298713 : return cachedFetchXidStatus;
63 : :
64 : : /*
65 : : * Also, check to see if the transaction ID is a permanent one.
66 : : */
8769 bruce@momjian.us 67 [ + + ]: 1373356 : if (!TransactionIdIsNormal(transactionId))
68 : : {
8830 tgl@sss.pgh.pa.us 69 [ + + ]: 664823 : if (TransactionIdEquals(transactionId, BootstrapTransactionId))
7789 70 : 660622 : return TRANSACTION_STATUS_COMMITTED;
8830 71 [ + + ]: 4201 : if (TransactionIdEquals(transactionId, FrozenTransactionId))
7789 72 : 4197 : return TRANSACTION_STATUS_COMMITTED;
73 : 4 : return TRANSACTION_STATUS_ABORTED;
74 : : }
75 : :
76 : : /*
77 : : * Get the transaction status.
78 : : */
6663 79 : 708533 : xidstatus = TransactionIdGetStatus(transactionId, &xidlsn);
80 : :
81 : : /*
82 : : * Cache it, but DO NOT cache status for unfinished or sub-committed
83 : : * transactions! We only cache status that is guaranteed not to change.
84 : : */
7789 85 [ + + + - ]: 708533 : if (xidstatus != TRANSACTION_STATUS_IN_PROGRESS &&
86 : : xidstatus != TRANSACTION_STATUS_SUB_COMMITTED)
87 : : {
6663 88 : 650290 : cachedFetchXid = transactionId;
7789 89 : 650290 : cachedFetchXidStatus = xidstatus;
6663 90 : 650290 : cachedCommitLSN = xidlsn;
91 : : }
92 : :
7789 93 : 708533 : return xidstatus;
94 : : }
95 : :
96 : : /* ----------------------------------------------------------------
97 : : * Interface functions
98 : : *
99 : : * TransactionIdDidCommit
100 : : * TransactionIdDidAbort
101 : : * ========
102 : : * these functions test the transaction status of
103 : : * a specified transaction id.
104 : : *
105 : : * TransactionIdCommitTree
106 : : * TransactionIdAsyncCommitTree
107 : : * TransactionIdAbortTree
108 : : * ========
109 : : * these functions set the transaction status of the specified
110 : : * transaction tree.
111 : : *
112 : : * See also TransactionIdIsInProgress, which once was in this module
113 : : * but now lives in procarray.c, as well as comments at the top of
114 : : * heapam_visibility.c that explain how everything fits together.
115 : : * ----------------------------------------------------------------
116 : : */
117 : :
118 : : /*
119 : : * TransactionIdDidCommit
120 : : * True iff transaction associated with the identifier did commit.
121 : : *
122 : : * Note:
123 : : * Assumes transaction identifier is valid and exists in clog.
124 : : */
125 : : bool /* true if given transaction committed */
10703 scrappy@hub.org 126 : 10645908 : TransactionIdDidCommit(TransactionId transactionId)
127 : : {
128 : : XidStatus xidstatus;
129 : :
7789 tgl@sss.pgh.pa.us 130 : 10645908 : xidstatus = TransactionLogFetch(transactionId);
131 : :
132 : : /*
133 : : * If it's marked committed, it's committed.
134 : : */
135 [ + + ]: 10645908 : if (xidstatus == TRANSACTION_STATUS_COMMITTED)
136 : 10516759 : return true;
137 : :
138 : : /*
139 : : * If it's marked subcommitted, we have to check the parent recursively.
140 : : * However, if it's older than TransactionXmin, we can't look at
141 : : * pg_subtrans; instead assume that the parent crashed without cleaning up
142 : : * its children.
143 : : *
144 : : * Originally we Assert'ed that the result of SubTransGetParent was not
145 : : * zero. However with the introduction of prepared transactions, there can
146 : : * be a window just after database startup where we do not have complete
147 : : * knowledge in pg_subtrans of the transactions after TransactionXmin.
148 : : * StartupSUBTRANS() has ensured that any missing information will be
149 : : * zeroed. Since this case should not happen under normal conditions, it
150 : : * seems reasonable to emit a WARNING for it.
151 : : */
152 [ - + ]: 129149 : if (xidstatus == TRANSACTION_STATUS_SUB_COMMITTED)
153 : : {
154 : : TransactionId parentXid;
155 : :
7712 tgl@sss.pgh.pa.us 156 [ # # ]:UBC 0 : if (TransactionIdPrecedes(transactionId, TransactionXmin))
7737 157 : 0 : return false;
7789 158 : 0 : parentXid = SubTransGetParent(transactionId);
7438 159 [ # # ]: 0 : if (!TransactionIdIsValid(parentXid))
160 : : {
161 [ # # ]: 0 : elog(WARNING, "no pg_subtrans entry for subcommitted XID %u",
162 : : transactionId);
163 : 0 : return false;
164 : : }
7789 165 : 0 : return TransactionIdDidCommit(parentXid);
166 : : }
167 : :
168 : : /*
169 : : * It's not committed.
170 : : */
7789 tgl@sss.pgh.pa.us 171 :CBC 129149 : return false;
172 : : }
173 : :
174 : : /*
175 : : * TransactionIdDidAbort
176 : : * True iff transaction associated with the identifier did abort.
177 : : *
178 : : * Note:
179 : : * Assumes transaction identifier is valid and exists in clog.
180 : : *
181 : : * Returns true only for explicitly aborted transactions, as transactions
182 : : * implicitly aborted due to a crash will commonly still appear to be
183 : : * in-progress in the clog. Most of the time TransactionIdDidCommit(),
184 : : * with a preceding TransactionIdIsInProgress() check, should be used
185 : : * instead of TransactionIdDidAbort().
186 : : */
187 : : bool /* true if given transaction aborted */
10703 scrappy@hub.org 188 : 26161 : TransactionIdDidAbort(TransactionId transactionId)
189 : : {
190 : : XidStatus xidstatus;
191 : :
7789 tgl@sss.pgh.pa.us 192 : 26161 : xidstatus = TransactionLogFetch(transactionId);
193 : :
194 : : /*
195 : : * If it's marked aborted, it's aborted.
196 : : */
197 [ + + ]: 26161 : if (xidstatus == TRANSACTION_STATUS_ABORTED)
198 : 18 : return true;
199 : :
200 : : /*
201 : : * If it's marked subcommitted, we have to check the parent recursively.
202 : : * However, if it's older than TransactionXmin, we can't look at
203 : : * pg_subtrans; instead assume that the parent crashed without cleaning up
204 : : * its children.
205 : : */
206 [ - + ]: 26143 : if (xidstatus == TRANSACTION_STATUS_SUB_COMMITTED)
207 : : {
208 : : TransactionId parentXid;
209 : :
7712 tgl@sss.pgh.pa.us 210 [ # # ]:UBC 0 : if (TransactionIdPrecedes(transactionId, TransactionXmin))
7737 211 : 0 : return true;
212 : 0 : parentXid = SubTransGetParent(transactionId);
7438 213 [ # # ]: 0 : if (!TransactionIdIsValid(parentXid))
214 : : {
215 : : /* see notes in TransactionIdDidCommit */
216 [ # # ]: 0 : elog(WARNING, "no pg_subtrans entry for subcommitted XID %u",
217 : : transactionId);
218 : 0 : return true;
219 : : }
7737 220 : 0 : return TransactionIdDidAbort(parentXid);
221 : : }
222 : :
223 : : /*
224 : : * It's not aborted.
225 : : */
7789 tgl@sss.pgh.pa.us 226 :CBC 26143 : return false;
227 : : }
228 : :
229 : : /*
230 : : * TransactionIdCommitTree
231 : : * Marks the given transaction and children as committed
232 : : *
233 : : * "xid" is a toplevel transaction commit, and the xids array contains its
234 : : * committed subtransactions.
235 : : *
236 : : * This commit operation is not guaranteed to be atomic, but if not, subxids
237 : : * are correctly marked subcommit first.
238 : : */
239 : : void
6217 alvherre@alvh.no-ip. 240 : 119861 : TransactionIdCommitTree(TransactionId xid, int nxids, TransactionId *xids)
241 : : {
242 : 119861 : TransactionIdSetTreeStatus(xid, nxids, xids,
243 : : TRANSACTION_STATUS_COMMITTED,
244 : : InvalidXLogRecPtr);
7789 tgl@sss.pgh.pa.us 245 : 119861 : }
246 : :
247 : : /*
248 : : * TransactionIdAsyncCommitTree
249 : : * Same as above, but for async commits. The commit record LSN is needed.
250 : : */
251 : : void
6217 alvherre@alvh.no-ip. 252 : 25806 : TransactionIdAsyncCommitTree(TransactionId xid, int nxids, TransactionId *xids,
253 : : XLogRecPtr lsn)
254 : : {
255 : 25806 : TransactionIdSetTreeStatus(xid, nxids, xids,
256 : : TRANSACTION_STATUS_COMMITTED, lsn);
6663 tgl@sss.pgh.pa.us 257 : 25806 : }
258 : :
259 : : /*
260 : : * TransactionIdAbortTree
261 : : * Marks the given transaction and children as aborted.
262 : : *
263 : : * "xid" is a toplevel transaction commit, and the xids array contains its
264 : : * committed subtransactions.
265 : : *
266 : : * We don't need to worry about the non-atomic behavior, since any onlookers
267 : : * will consider all the xacts as not-yet-committed anyway.
268 : : */
269 : : void
6217 alvherre@alvh.no-ip. 270 : 7786 : TransactionIdAbortTree(TransactionId xid, int nxids, TransactionId *xids)
271 : : {
272 : 7786 : TransactionIdSetTreeStatus(xid, nxids, xids,
273 : : TRANSACTION_STATUS_ABORTED, InvalidXLogRecPtr);
7789 tgl@sss.pgh.pa.us 274 : 7786 : }
275 : :
276 : :
277 : : /*
278 : : * TransactionIdLatest --- get latest XID among a main xact and its children
279 : : */
280 : : TransactionId
6625 281 : 170063 : TransactionIdLatest(TransactionId mainxid,
282 : : int nxids, const TransactionId *xids)
283 : : {
284 : : TransactionId result;
285 : :
286 : : /*
287 : : * In practice it is highly likely that the xids[] array is sorted, and so
288 : : * we could save some cycles by just taking the last child XID, but this
289 : : * probably isn't so performance-critical that it's worth depending on
290 : : * that assumption. But just to show we're not totally stupid, scan the
291 : : * array back-to-front to avoid useless assignments.
292 : : */
293 : 170063 : result = mainxid;
294 [ + + ]: 175493 : while (--nxids >= 0)
295 : : {
296 [ + + ]: 5430 : if (TransactionIdPrecedes(result, xids[nxids]))
297 : 666 : result = xids[nxids];
298 : : }
299 : 170063 : return result;
300 : : }
301 : :
302 : :
303 : : /*
304 : : * TransactionIdGetCommitLSN
305 : : *
306 : : * This function returns an LSN that is late enough to be able
307 : : * to guarantee that if we flush up to the LSN returned then we
308 : : * will have flushed the transaction's commit record to disk.
309 : : *
310 : : * The result is not necessarily the exact LSN of the transaction's
311 : : * commit record! For example, for long-past transactions (those whose
312 : : * clog pages already migrated to disk), we'll return InvalidXLogRecPtr.
313 : : * Also, because we group transactions on the same clog page to conserve
314 : : * storage, we might return the LSN of a later transaction that falls into
315 : : * the same group.
316 : : */
317 : : XLogRecPtr
6663 318 : 9435668 : TransactionIdGetCommitLSN(TransactionId xid)
319 : : {
320 : : XLogRecPtr result;
321 : :
322 : : /*
323 : : * Currently, all uses of this function are for xids that were just
324 : : * reported to be committed by TransactionLogFetch, so we expect that
325 : : * checking TransactionLogFetch's cache will usually succeed and avoid an
326 : : * extra trip to shared memory.
327 : : */
328 [ + + ]: 9435668 : if (TransactionIdEquals(xid, cachedFetchXid))
329 : 8795818 : return cachedCommitLSN;
330 : :
331 : : /* Special XIDs are always known committed */
332 [ + - ]: 639850 : if (!TransactionIdIsNormal(xid))
333 : 639850 : return InvalidXLogRecPtr;
334 : :
335 : : /*
336 : : * Get the transaction status.
337 : : */
6663 tgl@sss.pgh.pa.us 338 :UBC 0 : (void) TransactionIdGetStatus(xid, &result);
339 : :
340 : 0 : return result;
341 : : }
|