Skip to content

Commit 7b549b2

Browse files
pjungwirCommitfest Bot
authored andcommitted
Add isolation tests for UPDATE/DELETE FOR PORTION OF
Concurrent updates/deletes in READ COMMITTED mode don't give you what you want: the second update/delete fails to leftovers from the first, so you essentially have lost updates/deletes. But we are following the rules, and other RDBMSes give you screwy results in READ COMMITTED too (albeit different). One approach is to lock the history you want with SELECT FOR UPDATE before issuing the actual UPDATE/DELETE. That way you see the leftovers of anyone else who also touched that history. The isolation tests here use that approach and show that it's viable.
1 parent d415afc commit 7b549b2

File tree

5 files changed

+6575
-0
lines changed

5 files changed

+6575
-0
lines changed

doc/src/sgml/dml.sgml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,22 @@ WHERE id = 5;
394394
column references are not.
395395
</para>
396396

397+
<para>
398+
In <literal>READ COMMITTED</literal> mode, temporal updates and deletes can
399+
cause unexpected results when they concurrently touch the same row. It is
400+
possible to lose all or part of the second update or delete. That's because
401+
after the first update changes the start/end times of the original
402+
record, it may no longer fit within the second query's <literal>FOR PORTION
403+
OF</literal> bounds, so it becomes disqualified from the query. On the other
404+
hand the just-inserted temporal leftovers may be overlooked by the second query,
405+
which has already scanned the table to find rows to modify. To solve these
406+
problems, precede every temporal update/delete with a <literal>SELECT FOR
407+
UPDATE</literal> matching the same criteria (including the targeted portion of
408+
application time). That way the actual update/delete doesn't begin until the
409+
lock is held, and all concurrent leftovers will be visible. In other
410+
transaction isolation levels, this lock is not required.
411+
</para>
412+
397413
<para>
398414
When temporal leftovers are inserted, all <literal>INSERT</literal>
399415
triggers are fired, but permission checks for inserting rows are

src/backend/executor/nodeModifyTable.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,10 @@ ExecForPortionOfLeftovers(ModifyTableContext *context,
14031403
* We have already locked the tuple in ExecUpdate/ExecDelete, and it has
14041404
* passed EvalPlanQual. This ensures that concurrent updates in READ
14051405
* COMMITTED can't insert conflicting temporal leftovers.
1406+
*
1407+
* It does *not* protect against concurrent update/deletes overlooking each
1408+
* others' leftovers though. See our isolation tests for details about that
1409+
* and a viable workaround.
14061410
*/
14071411
if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc, tupleid, SnapshotAny, oldtupleSlot))
14081412
elog(ERROR, "failed to fetch tuple for FOR PORTION OF");

0 commit comments

Comments
 (0)