mirror of
https://github.com/postgres/postgres.git
synced 2025-05-05 09:19:17 +03:00
Fix concurrent locking of tuple update chain
If several sessions are concurrently locking a tuple update chain with nonconflicting lock modes using an old snapshot, and they all succeed, it may happen that some of them fail because of restarting the loop (due to a concurrent Xmax change) and getting an error in the subsequent pass while trying to obtain a tuple lock that they already have in some tuple version. This can only happen with very high concurrency (where a row is being both updated and FK-checked by multiple transactions concurrently), but it's been observed in the field and can have unpleasant consequences such as an FK check failing to see a tuple that definitely exists: ERROR: insert or update on table "child_table" violates foreign key constraint "fk_constraint_name" DETAIL: Key (keyid)=(123456) is not present in table "parent_table". (where the key is observably present in the table). Discussion: https://postgr.es/m/20170714210011.r25mrff4nxjhmf3g@alvherre.pgsql
This commit is contained in:
parent
3b7bbee7b6
commit
8c348765f9
@ -5550,8 +5550,10 @@ l5:
|
|||||||
* with the given xid, does the current transaction need to wait, fail, or can
|
* with the given xid, does the current transaction need to wait, fail, or can
|
||||||
* it continue if it wanted to acquire a lock of the given mode? "needwait"
|
* it continue if it wanted to acquire a lock of the given mode? "needwait"
|
||||||
* is set to true if waiting is necessary; if it can continue, then
|
* is set to true if waiting is necessary; if it can continue, then
|
||||||
* HeapTupleMayBeUpdated is returned. In case of a conflict, a different
|
* HeapTupleMayBeUpdated is returned. If the lock is already held by the
|
||||||
* HeapTupleSatisfiesUpdate return code is returned.
|
* current transaction, return HeapTupleSelfUpdated. In case of a conflict
|
||||||
|
* with another transaction, a different HeapTupleSatisfiesUpdate return code
|
||||||
|
* is returned.
|
||||||
*
|
*
|
||||||
* The held status is said to be hypothetical because it might correspond to a
|
* The held status is said to be hypothetical because it might correspond to a
|
||||||
* lock held by a single Xid, i.e. not a real MultiXactId; we express it this
|
* lock held by a single Xid, i.e. not a real MultiXactId; we express it this
|
||||||
@ -5574,8 +5576,9 @@ test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid,
|
|||||||
if (TransactionIdIsCurrentTransactionId(xid))
|
if (TransactionIdIsCurrentTransactionId(xid))
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Updated by our own transaction? Just return failure. This
|
* The tuple has already been locked by our own transaction. This is
|
||||||
* shouldn't normally happen.
|
* very rare but can happen if multiple transactions are trying to
|
||||||
|
* lock an ancient version of the same tuple.
|
||||||
*/
|
*/
|
||||||
return HeapTupleSelfUpdated;
|
return HeapTupleSelfUpdated;
|
||||||
}
|
}
|
||||||
@ -5774,6 +5777,22 @@ l4:
|
|||||||
members[i].xid,
|
members[i].xid,
|
||||||
mode, &needwait);
|
mode, &needwait);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the tuple was already locked by ourselves in a
|
||||||
|
* previous iteration of this (say heap_lock_tuple was
|
||||||
|
* forced to restart the locking loop because of a change
|
||||||
|
* in xmax), then we hold the lock already on this tuple
|
||||||
|
* version and we don't need to do anything; and this is
|
||||||
|
* not an error condition either. We just need to skip
|
||||||
|
* this tuple and continue locking the next version in the
|
||||||
|
* update chain.
|
||||||
|
*/
|
||||||
|
if (result == HeapTupleSelfUpdated)
|
||||||
|
{
|
||||||
|
pfree(members);
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
|
||||||
if (needwait)
|
if (needwait)
|
||||||
{
|
{
|
||||||
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
|
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
|
||||||
@ -5834,6 +5853,19 @@ l4:
|
|||||||
|
|
||||||
result = test_lockmode_for_conflict(status, rawxmax, mode,
|
result = test_lockmode_for_conflict(status, rawxmax, mode,
|
||||||
&needwait);
|
&needwait);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the tuple was already locked by ourselves in a previous
|
||||||
|
* iteration of this (say heap_lock_tuple was forced to
|
||||||
|
* restart the locking loop because of a change in xmax), then
|
||||||
|
* we hold the lock already on this tuple version and we don't
|
||||||
|
* need to do anything; and this is not an error condition
|
||||||
|
* either. We just need to skip this tuple and continue
|
||||||
|
* locking the next version in the update chain.
|
||||||
|
*/
|
||||||
|
if (result == HeapTupleSelfUpdated)
|
||||||
|
goto next;
|
||||||
|
|
||||||
if (needwait)
|
if (needwait)
|
||||||
{
|
{
|
||||||
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
|
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
|
||||||
@ -5894,6 +5926,7 @@ l4:
|
|||||||
|
|
||||||
END_CRIT_SECTION();
|
END_CRIT_SECTION();
|
||||||
|
|
||||||
|
next:
|
||||||
/* if we find the end of update chain, we're done. */
|
/* if we find the end of update chain, we're done. */
|
||||||
if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
|
if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
|
||||||
ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
|
ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
|
||||||
|
Loading…
x
Reference in New Issue
Block a user