Un-indexed foreign keys are the number one reason for deadlocks in
database. Here is a scenario observed
recently in a customer code base.
Here is the criteria for the test case.
- The child table should have more than one un-indexed foreign key to parent table.
- Primary key on child table should have *any* one of those un-indexed foreign key column on the leading edge of primary key definition.
- A trigger has the *potential* to change a primary key column in parent. (You can see from the trigger that this primary key will never *change*, but I don’t think optimizer is smart enough to deduce that, the moment the database see “:new.primary_key_col” as a potential assignment target, it assumes the worst)
Here is the test case to demonstrate that.
rajesh@ORA11G> set feedback off
rajesh@ORA11G> create table p(x1 int primary key,data
varchar2(10));
rajesh@ORA11G> create table c(x int, x1 references p,x2
references p,
2 data varchar2(10),
3 constraint c_pk primary
key(x2,x));
rajesh@ORA11G> insert into p values(1,'Data1');
rajesh@ORA11G> insert into p values(2,'Data2');
rajesh@ORA11G> commit;
rajesh@ORA11G> create or replace trigger p_trig
2 before insert or update on P
3 for each row
4 declare
5 k int;
6 begin
7 if 1=2 then
8 :new.x1 := k ;
9 end if;
10 end;
11 /
rajesh@ORA11G> set feedback on
rajesh@ORA11G>
rajesh@ORA11G> select * from p;
X1 DATA
---------- ----------
1 Data1
2 Data2
2 rows selected.
rajesh@ORA11G> select * from c;
no rows selected
When
we tried to update the parent table (a non-key column) which in-turn executed
the trigger and looking at the code base
“:new.primary_key_column = k” primary key as assignment target which is
bounded inside a False Fake statement
1=2 , will never be True in this case, so the primary key doesn’t
change.
But
optimizer and Pl/SQL compiler is not able to deduce that and places a Full
table lock on the child table.
rajesh@ORA11G> update p set data ='XX' where x1=1;
1 row updated.
rajesh@ORA11G> select t2.object_name, t2.object_type,
t1.locked_mode
2 from v$locked_object t1,
3 user_objects t2
4 where t1.object_id = t2.object_id ;
OBJECT_NAME
OBJECT_TYPE LOCKED_MODE
------------------------------ ------------------- -----------
C
TABLE 3
P TABLE 3
2 rows selected.
Remember
lock_mode=3 is Row Exclusive Table lock (SX) is more restrictive than a share
table lock. Only one transaction at a time can acquire an SX lock on a given
table. An SX lock held by a transaction allows other transactions to query the
table (except for SELECT ... FOR UPDATE) but not to update the table.
When
a different transaction tries to place Full Table lock on child table will lead
to dead lock.
rajesh@ORA11G> declare
2 pragma autonomous_transaction;
3 begin
4 update p set data ='YY' where x1=2;
5 rollback;
6 end;
7 /
declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for
resource
ORA-06512: at line 4
rajesh@ORA11G>
Is
this a bug? To be honest, I don’t know. The resolution/workaround is of course,
as per normal - add those indexes on foreign key columns.
Addendum:
o
This behavior is still reproducible on the
latest version of oracle (12.1.0.2)
o
If primary key is not defined on the child table,
then no deadlock.
o
If the primary key on the child table don’t
include the foreign key column then no deadlock. ( that is if primary key is defined on C(X)
then no deadlock)
o
If the primary key on the child table don’t
include the foreign key column in its leading edge then no deadlock ( that is
if primary key is defined on C(X,X2) or C(X,X3) then no deadlock, instead if
primary key is defined as C(X2,X) or
C(X3,X) then deadlock).
No comments:
Post a Comment