RI = Referential Integrity: also known informally as parent/child integrity, and primary (or unique) key/foreign key checking.
I’m on a bit of a roll with things that I must have explained dozens or even hundreds of times in different environments without ever formally explaining them on my blog. Here’s a blog item I could have done with to response to a question that came up on the OTN database forum over the weekend.
What happens in the following scenario:
-- session 1 create table parent ( id number(8,0), constraint par_pk primary key(id) ); create table child ( id_p number(8,0) not null references parent, id_c number(8,0) not null, constraint child_pk primary key(id_p, id_c) ) ; insert into parent values(1); -- session 2 insert into child values(1,1);
Since the parent row corresponding to the child row doesn’t (yet) seem to exist as far as session 2 is concerned you might expect session 2 to respond immediately with an error message like:
ERROR at line 1: ORA-02291: integrity constraint (TEST_USER.SYS_C0017926) violated - parent key not found
In fact, although the end-user is not allowed to see the uncommitted parent row, the user’s process can see the uncommitted row and will wait until session 1 commits or rolls back – so if you examine v$lock for the current locks for the two sessions you’d see something like this:
1 select sid, type, id1, id2, lmode, request, ctime, block 2 from V$lock 3 where sid in (select sid from V$session where username = 'TEST_USER') 4 and type != 'AE' 5 order by 6* sid, type desc 7 / SID TY ID1 ID2 LMODE REQUEST CTIME BLOCK ---------- -- ---------- ---------- ---------- ---------- ---------- ---------- 3 TX 327709 12584 6 0 283 1 TM 143734 0 2 0 283 0 TM 143732 0 3 0 283 0 250 TX 589829 12877 6 0 240 0 TX 327709 12584 0 4 240 0 TM 143734 0 3 0 240 0 TM 143732 0 3 0 240 0 7 rows selected.
In the above, SID 250 is session 2: it’s holding a transaction lock (TX) in mode 6 because it has acquired an undo segment and has generated some undo, it’s also waiting for a transaction lock in mode 4 (share) and – checking id1 and id2 – we can see that the transaction table entry it’s waiting for is held by session 3 in mode 6 (and we also note that the lock held by session 3 is marked as a blocker).
If session 3 commits (thus releasing the transaction lock) session 250 will continue processing the insert; if session 3 rolls back session 250 will raise error ORA-02291 and roll back its insert statement. (Note: if this were a multi-statement transaction it would only be the insert into child that would be rolled back; that’s another one of those details that is important but often isn’t stated explicitly, leaving people believing that the entire transaction would be rolled back.)
Updates and deletes can produce the same effects. Imagine that we have just created the two tables, and then run the following:
-- session 1 insert into parent values(1); commit; delete from parent where id = 1; -- session 2 insert into child values(1,1);
Again session 2 will wait for session 1 to commit or roll back. In this case if session 1 commits session 2 will raise Oracle error ORA-02291, if session 1 rolls back session 2 will continue with the insert.
Deadlocks
Whenever you can demonstrate a way of producing a wait chain you can also manage to produce a deadlock. Consider the following (starting, again, from empty tables);
-- (1) session 1 insert into parent values(1); -- (2) session 2 insert into parent values(2); -- (3) session 1 insert into child values(2,2); -- (4)session 2 insert into child values(1,1);
Session 1 will start waiting for session 2 to commit (or rollback) at step 3, then session 2 will start to wait for session 1 at step 4 – with the result that session 1 will recognise the deadlock after about three seconds and rollback its last statement, raising exception ORA-00060 and dumping a trace file. (Note: session 1 will not, as many people think, roll back the entire transaction, it will only roll back the statement that allowed the deadlock to develop). Session 2 will still be waiting for session 1 to commit or rollback its insert into parent. Contrary to the popular claim, Oracle will not “resolve” the deadlock, it will simply break the deadlock leaving one session waiting for the other session to respond appropriately to the deadlock error.
For reference, here’s the deadlock graph (from a 12c trace file) produced by session 1 (SID = 3) for this demo:
Deadlock graph: ---------Blocker(s)-------- ---------Waiter(s)--------- Resource Name process session holds waits process session holds waits TX-00010017-000026C7-00000000-00000000 6 3 X 33 250 S TX-000A000D-000026F8-00000000-00000000 33 250 X 6 3 S session 3: DID 0001-0006-00000004 session 250: DID 0001-0021-00000041 session 250: DID 0001-0021-00000041 session 3: DID 0001-0006-00000004 Rows waited on: Session 3: no row Session 250: no row
When you see a deadlock graph with TX waits of type S (share, mode 4) it’s a very good bet that the wait has something to do with indexes – which may mean referential integrity as discussed here, but may mean collisions on primary keys, and may mean something to do with simple collisions on index-organized tables. You’ll notice that the “Rows waited on:” section shows no row – unfortunately in earlier versions of Oracle you may find a spurious row entry here because the wait information from some other (block) wait has been left in the relevant columns in v$session.
