Oracle FAQ | Your Portal to the Oracle Knowledge Grid |
Home -> Community -> Usenet -> c.d.o.server -> Single-statement 'write consistency' on read committed. Oh, really?
Please note that in the following I'm only concerned about
single-statement read committed transactions. I do realize that for a
multi-statement read committed transaction Oracle does not guarantee
transaction set consistency without techniques like select for update
or explicit hand-coded locking.
According to the documentation Oracle guarantees 'statement-level transaction set consistency' for queries in read committed transactions. In many cases, Oracle also provides single-statement write consistency. However, when an update based on a consistent read tries to overwrite changes committed by other transactions after the statement started, it creates a write conflict. Oracle never reports write conflicts on read committed. Instead, it automatically handles them based on the new values for the target table columns referenced by the update.
Let's consider a simple example. Again, I do realize that the following design might look strange or even sloppy, but the ability to produce a quality design when needed is not an issue here. I'm simply trying to understand the Oracle's behavior on write conflicts in a single-statement read committed transaction.
A valid business case behind the example is rather common - a financial institution with two-stage funds transfer processing. First, you submit a transfer (put transfer amounts in the 'pending' column of the account) in case the whole financial transaction is in doubt. Second, after you got all the necessary confirmations you clear all the pending transfers making the corresponding account balance changes, resetting pending amount and marking the accounts cleared by setting the cleared date. Neither stage should leave the data in inconsistent state: sum (amount) for all rows should not change and the sum (pending) for all rows should always be 0 on either stage:
Setup:
create table accounts
(
acc int primary key, amount int, pending int, cleared date
Initially the table contains the following:
ACC AMOUNT PENDING CLEARED
update accounts
set pending = pending - 1, cleared = null where acc = 1;
update accounts
set pending = pending + 1, cleared = null where acc = 3;
ACC AMOUNT PENDING CLEARED
And now let's clear all the pending transfers in SQL*Plus Session 2 in a single-statement read-committed transaction:
update accounts
set amount = amount + pending, pending = 0, cleared = sysdate
where cleared is null;
Session 2 naturally blocks. Now commit the transaction in session 1. Session 2 readily unblocks:
ACC AMOUNT PENDING CLEARED
Here we go - the results produced by a single-statement transaction read committed transaction in session 2, are inconsistent – the second funds transfer has not completed in full. Session 2 should have produced the following instead:
ACC AMOUNT PENDING CLEARED
But if in the session 2 instead of:
we issued:
or even:
We'd have gotten what we really wanted.
I'm very well aware of the 'select for update' or serializable il solution for the problem. Also, I could present a working example for precisely the above scenario for a major database product, providing the results that I would consider to be correct. That is, the interleaving execution of the transactions has the same effect as if they completed serially. Naturally, no extra hand-coded locking techniques like select for update or explicit locking is involved.
And now let's try to understand what just has happened. Playing around with similar trivial scenarios one could easily figure out that Oracle clearly employs different strategies when handling update conflicts based on the new values for the target table columns, referenced by the update. I have observed the following cases:
And here come the questions, assuming that (B) is not a bug, but the expected behavior:
If, on the other hand, it's been documented, could anybody please point me to the bit in the documentation that: