Skip to Main Content
  • Questions
  • Subquery consistency in an update restart

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Tony.

Asked: June 10, 2024 - 1:27 pm UTC

Last updated: June 11, 2024 - 5:44 am UTC

Version: 11.2.0.1.0

Viewed 1000+ times

You Asked

I learned from the documentation of 11g that “Oracle Database always enforces statement-level read consistency”, which I think should mean that every query in a single statement (including subqueries, excluding queries in PL/SQL functions) is consistent to a single point in time (in the read committed isolation level, this point is the time at which the statement was opened).
But I also read from an older documentation that noted “Transactions containing DML statements with subqueries should use serializable isolation to guarantee consistent read” (I didn’t find it in recent versions of documentations). https://docs.oracle.com/cd/B10501_01/server.920/a96524/c21cnsis.htm

Here’s what I tried in 11g to test out subquery consistency in an update restart:
I created the table below with a before update trigger for each row that prints out a message, so that I can know how many time it’s fired.

create table T
(
    ID  NUMBER(1),
    NUM NUMBER(1)
)
/
create trigger TEST_TRIGGER
    before update
    on T
    for each row
begin
    dbms_output.put_line('fired');
end;
/
INSERT INTO T (ID, NUM) VALUES (1, 1);
INSERT INTO T (ID, NUM) VALUES (2, 2);


Example 1:
Session 1: update t set NUM = 3 where 1 = 1
Session 2: set serveroutput on
Session 2: update t set NUM = (select NUM + 3 from t where ID = 1) where NUM > 1; (blocked by session 1)
Session 1: COMMIT
Session 2: The trigger fired 3 times which means there’s a restart, and two rows were updated.
Session 2: COMMIT

The NUM columns are all set to 4 in the end, which means the subquery didn’t rerun when the update restart happened. The subquery still reads the NUM of row 1 as 1 as of the start of the statement, while the implicit query of the update reads it as 3 as of the restart, which creates inconsistency.

Example 2:
Session 1: update t set NUM = 3 where 1 = 1;
Session 2: set serveroutput on
Session 2: update t a set NUM = (select b.NUM + 3 from t b where b.ID = a.ID) where NUM > 1; (blocked by session 1)
Session 1: COMMIT
Session 2: The trigger fired 3 times which means there’s a restart, and two rows were updated.
Session 2: COMMIT

The NUM columns are all set to 6 in the end, which means both the subquery and the implicit query read data as of the restart.


So here’s my guess:
1.If there’s no update restart, queries within a single statement are guaranteed to be consistent as of the start of the statement.
2.If there is an update restart, correlated subquery will rerun since it’s executed per row, like the implicit query, it’s also consistent as of the restart.
3.If there is an update restart, non-correlated subquery won’t rerun since it’s executed per query, which makes inconsistency possible.

Am I understanding it correctly? Please clarify!

and Connor said...

Yes, you are correct but there is plenty of nuance here.

Rather than reproduce existing content, check Franck and Hatem's really good writeups on the topic.

https://www.dbi-services.com/blog/oracle-write-consistency-bug-and-multi-thread-de-queuing/

https://mahmoudhatem.wordpress.com/2018/11/15/dml-restart-and-tracked-columns/

Because of (a) the existence of SKIP LOCKED, and (b) that these issues are typically in niche circumstances, I don't see these issues being tackled natively in the kernel any time soon.

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library