📄 heapam.c
字号:
HEAP_IS_LOCKED | HEAP_MOVED); HeapTupleHeaderSetXmax(oldtup.t_data, xid); HeapTupleHeaderSetCmax(oldtup.t_data, cid); /* temporarily make it look not-updated */ oldtup.t_data->t_ctid = oldtup.t_self; already_marked = true; LockBuffer(buffer, BUFFER_LOCK_UNLOCK); /* * Let the toaster do its thing, if needed. * * Note: below this point, heaptup is the data we actually intend to * store into the relation; newtup is the caller's original untoasted * data. */ if (need_toast) { heaptup = toast_insert_or_update(relation, newtup, &oldtup); newtupsize = MAXALIGN(heaptup->t_len); } else heaptup = newtup; /* * Now, do we need a new page for the tuple, or not? This is a bit * tricky since someone else could have added tuples to the page while * we weren't looking. We have to recheck the available space after * reacquiring the buffer lock. But don't bother to do that if the * former amount of free space is still not enough; it's unlikely * there's more free now than before. * * What's more, if we need to get a new page, we will need to acquire * buffer locks on both old and new pages. To avoid deadlock against * some other backend trying to get the same two locks in the other * order, we must be consistent about the order we get the locks in. * We use the rule "lock the lower-numbered page of the relation * first". To implement this, we must do RelationGetBufferForTuple * while not holding the lock on the old page, and we must rely on it * to get the locks on both pages in the correct order. */ if (newtupsize > pagefree) { /* Assume there's no chance to put heaptup on same page. */ newbuf = RelationGetBufferForTuple(relation, heaptup->t_len, buffer, true); } else { /* Re-acquire the lock on the old tuple's page. */ LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); /* Re-check using the up-to-date free space */ pagefree = PageGetFreeSpace((Page) dp); if (newtupsize > pagefree) { /* * Rats, it doesn't fit anymore. We must now unlock and * relock to avoid deadlock. Fortunately, this path should * seldom be taken. */ LockBuffer(buffer, BUFFER_LOCK_UNLOCK); newbuf = RelationGetBufferForTuple(relation, heaptup->t_len, buffer, true); } else { /* OK, it fits here, so we're done. */ newbuf = buffer; } } } else { /* No TOAST work needed, and it'll fit on same page */ already_marked = false; newbuf = buffer; heaptup = newtup; } /* * At this point newbuf and buffer are both pinned and locked, and newbuf * has enough space for the new tuple. If they are the same buffer, only * one pin is held. */ /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); RelationPutHeapTuple(relation, newbuf, heaptup); /* insert new tuple */ if (!already_marked) { oldtup.t_data->t_infomask &= ~(HEAP_XMAX_COMMITTED | HEAP_XMAX_INVALID | HEAP_XMAX_IS_MULTI | HEAP_IS_LOCKED | HEAP_MOVED); HeapTupleHeaderSetXmax(oldtup.t_data, xid); HeapTupleHeaderSetCmax(oldtup.t_data, cid); } /* record address of new tuple in t_ctid of old one */ oldtup.t_data->t_ctid = heaptup->t_self; /* XLOG stuff */ if (!relation->rd_istemp) { XLogRecPtr recptr = log_heap_update(relation, buffer, oldtup.t_self, newbuf, heaptup, false); if (newbuf != buffer) { PageSetLSN(BufferGetPage(newbuf), recptr); PageSetTLI(BufferGetPage(newbuf), ThisTimeLineID); } PageSetLSN(BufferGetPage(buffer), recptr); PageSetTLI(BufferGetPage(buffer), ThisTimeLineID); } else { /* No XLOG record, but still need to flag that XID exists on disk */ MyXactMadeTempRelUpdate = true; } END_CRIT_SECTION(); if (newbuf != buffer) LockBuffer(newbuf, BUFFER_LOCK_UNLOCK); LockBuffer(buffer, BUFFER_LOCK_UNLOCK); /* * Mark old tuple for invalidation from system caches at next command * boundary. We have to do this before WriteBuffer because we need to look * at the contents of the tuple, so we need to hold our refcount. */ CacheInvalidateHeapTuple(relation, &oldtup); if (newbuf != buffer) WriteBuffer(newbuf); WriteBuffer(buffer); /* * If new tuple is cachable, mark it for invalidation from the caches in * case we abort. Note it is OK to do this after WriteBuffer releases the * buffer, because the heaptup data structure is all in local memory, not * in the shared buffer. */ CacheInvalidateHeapTuple(relation, heaptup); /* * Release the lmgr tuple lock, if we had it. */ if (have_tuple_lock) UnlockTuple(relation, &(oldtup.t_self), ExclusiveLock); pgstat_count_heap_update(&relation->pgstat_info); /* * If heaptup is a private copy, release it. Don't forget to copy t_self * back to the caller's image, too. */ if (heaptup != newtup) { newtup->t_self = heaptup->t_self; heap_freetuple(heaptup); } return HeapTupleMayBeUpdated;}/* * simple_heap_update - replace a tuple * * This routine may be used to update a tuple when concurrent updates of * the target tuple are not expected (for example, because we have a lock * on the relation associated with the tuple). Any failure is reported * via ereport(). */voidsimple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup){ HTSU_Result result; ItemPointerData update_ctid; TransactionId update_xmax; result = heap_update(relation, otid, tup, &update_ctid, &update_xmax, GetCurrentCommandId(), InvalidSnapshot, true /* wait for commit */ ); switch (result) { case HeapTupleSelfUpdated: /* Tuple was already updated in current command? */ elog(ERROR, "tuple already updated by self"); break; case HeapTupleMayBeUpdated: /* done successfully */ break; case HeapTupleUpdated: elog(ERROR, "tuple concurrently updated"); break; default: elog(ERROR, "unrecognized heap_update status: %u", result); break; }}/* * heap_lock_tuple - lock a tuple in shared or exclusive mode * * Note that this acquires a buffer pin, which the caller must release. * * Input parameters: * relation: relation containing tuple (caller must hold suitable lock) * tuple->t_self: TID of tuple to lock (rest of struct need not be valid) * cid: current command ID (used for visibility test, and stored into * tuple's cmax if lock is successful) * mode: indicates if shared or exclusive tuple lock is desired * nowait: if true, ereport rather than blocking if lock not available * * Output parameters: * *tuple: all fields filled in * *buffer: set to buffer holding tuple (pinned but not locked at exit) * *ctid: set to tuple's t_ctid, but only in failure cases * *update_xmax: set to tuple's xmax, but only in failure cases * * Function result may be: * HeapTupleMayBeUpdated: lock was successfully acquired * HeapTupleSelfUpdated: lock failed because tuple updated by self * HeapTupleUpdated: lock failed because tuple updated by other xact * * In the failure cases, the routine returns the tuple's t_ctid and t_xmax. * If t_ctid is the same as t_self, the tuple was deleted; if different, the * tuple was updated, and t_ctid is the location of the replacement tuple. * (t_xmax is needed to verify that the replacement tuple matches.) * * * NOTES: because the shared-memory lock table is of finite size, but users * could reasonably want to lock large numbers of tuples, we do not rely on * the standard lock manager to store tuple-level locks over the long term. * Instead, a tuple is marked as locked by setting the current transaction's * XID as its XMAX, and setting additional infomask bits to distinguish this * usage from the more normal case of having deleted the tuple. When * multiple transactions concurrently share-lock a tuple, the first locker's * XID is replaced in XMAX with a MultiTransactionId representing the set of * XIDs currently holding share-locks. * * When it is necessary to wait for a tuple-level lock to be released, the * basic delay is provided by XactLockTableWait or MultiXactIdWait on the * contents of the tuple's XMAX. However, that mechanism will release all * waiters concurrently, so there would be a race condition as to which * waiter gets the tuple, potentially leading to indefinite starvation of * some waiters. The possibility of share-locking makes the problem much * worse --- a steady stream of share-lockers can easily block an exclusive * locker forever. To provide more reliable semantics about who gets a * tuple-level lock first, we use the standard lock manager. The protocol * for waiting for a tuple-level lock is really * LockTuple() * XactLockTableWait() * mark tuple as locked by me * UnlockTuple() * When there are multiple waiters, arbitration of who is to get the lock next * is provided by LockTuple(). However, at most one tuple-level lock will * be held or awaited per backend at any time, so we don't risk overflow * of the lock table. Note that incoming share-lockers are required to * do LockTuple as well, if there is any conflict, to ensure that they don't * starve out waiting exclusive-lockers. However, if there is not any active * conflict for a tuple, we don't incur any extra overhead. */HTSU_Resultheap_lock_tuple(Relation relation, HeapTuple tuple, Buffer *buffer, ItemPointer ctid, TransactionId *update_xmax, CommandId cid, LockTupleMode mode, bool nowait){ HTSU_Result result; ItemPointer tid = &(tuple->t_self); ItemId lp; PageHeader dp; TransactionId xid; uint16 new_infomask; LOCKMODE tuple_lock_type; bool have_tuple_lock = false; tuple_lock_type = (mode == LockTupleShared) ? ShareLock : ExclusiveLock; *buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid)); LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE); dp = (PageHeader) BufferGetPage(*buffer); lp = PageGetItemId(dp, ItemPointerGetOffsetNumber(tid)); Assert(ItemIdIsUsed(lp)); tuple->t_datamcxt = NULL; tuple->t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp); tuple->t_len = ItemIdGetLength(lp); tuple->t_tableOid = RelationGetRelid(relation);l3: result = HeapTupleSatisfiesUpdate(tuple->t_data, cid, *buffer); if (result == HeapTupleInvisible) { LockBuffer(*buffer, BUFFER_LOCK_UNLOCK); ReleaseBuffer(*buffer); elog(ERROR, "attempted to lock invisible tuple"); } else if (result == HeapTupleBeingUpdated) { TransactionId xwait; uint16 infomask; /* must copy state data before unlocking buffer */ xwait = HeapTupleHeaderGetXmax(tuple->t_data); infomask = tuple->t_data->t_infomask; LockBuffer(*buffer, BUFFER_LOCK_UNLOCK); /* * Acquire tuple lock to establish our priority for the tuple. * LockTuple will release us when we are next-in-line for the tuple. * We must do this even if we are share-locking. * * If we are forced to "start over" below, we keep the tuple lock; * this arranges that we stay at the head of the line while rechecking * tuple state. */ if (!have_tuple_lock) { if (nowait) { if (!ConditionalLockTuple(relation, tid, tuple_lock_type)) ereport(ERROR, (errcode(ERRCODE_LOCK_NOT_AVAILABLE), errmsg("could not obtain lock on row in relation \"%s\"", RelationGetRelationName(relation)))); } else LockTuple(relation, tid, tuple_lock_type); have_tuple_lock = true; } if (mode == LockTupleShared && (infomask & HEAP_XMAX_SHARED_LOCK)) { /* * Acquiring sharelock when there's at least one sharelocker * already. We need not wait for him/them to complete. */ LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE); /* * Make sure it's still a shared lock, else start over. (It's OK * if the ownership of the shared lock has changed, though.) */ if (!(tuple->t_data->t_infomask & HEAP_XMAX_SHARED_LOCK)) goto l3; } else if (infomask & HEAP_XMAX_IS_MULTI) { /* wait for multixact to end */ if (nowait) { if (!ConditionalMultiXactIdWait((MultiXactId) xwait)) ereport(ERROR, (errcode(ERRCODE_LOCK_NOT_AVAILABLE), errmsg("could not obtain lock on row in relation \"%s\"", RelationGetRelationName(relation)))); } else MultiXactIdWait((MultiXactId) xwait); LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE); /* * If xwait had just locked the tuple then some other xact could * update this tuple before we get to this point. Check for xmax * change, and start over if so. */ if (!(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI) || !TransactionIdEquals(HeapTupleHeaderGetXmax(tuple->t_data), xwait)) goto l3; /* * You might think the multixact is necessarily done here, but not * so: it could have surviving members, namely our own xact or * other subxacts of this backend. It is legal for us to lock the * tuple in either case, however. We don't bother changing the * on-disk hint bits since we are about to overwrite the xmax * altogether. */ } else { /* wait for regular transaction to end */ if (nowait) { if (!ConditionalXactLockTableWait(xwait)) ereport(ERROR, (errcode(ERRCODE_LOCK_NOT_AVAILABLE), errmsg("could not obtain lock on row in relation \"%s\"", RelationGetRelationName(relation)))); } else XactLockTableWait(xwait); LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE); /* * xwait is done, but if xwait had just locked the tuple then some * other xact could update this tuple before we get to this point. * Check for xmax change, and start over if so. */ if ((tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI) || !TransactionIdEquals(HeapTupleHeaderGetXmax(tuple->t_data), xwait)) goto l3; /* Otherwise we can mark it committed or aborted */ if (!(tuple->t_data->t_infomask & (HEAP_XMAX_COMMITTED | HEAP_XMAX_INVALID))) { if (TransactionIdDidCommit(xwait)) tuple->t_data->t_infomask |= HEAP_XMAX_COMMITTED; else tuple->t_data->t_infomask |= HEAP_XMAX_INVALID; SetBufferCommitInfoNeedsSave(*buffer); } } /* * We may lock if previous xmax aborted, or if it committed but only * locked the tuple without updating it. The case where we didn't * wait because we are joining an existing shared lock is correctly * handled, too. */ if (tuple->t_data->t_infomask & (HEAP_XMAX_INVALID | HEAP_IS_LOCKED)) result = HeapTupleMayBeUpdated; else result = HeapTupleUpdated; } if (result != HeapTupleMayBeUpdated) { Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated); Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVAL
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -