This comes down to the order the database tries to acquire locks on the rows.
In your example objectid = 1 is "first" in the table. You can verify this by sorting the data by rowid:
create table test_table
(
objectId VARCHAR2(40) not null,
dependId VARCHAR2(40) not null
);
insert into test_table values(1, 99);
insert into test_table values(2, 0);
commit;
select rowid, t.* from test_table t
order by rowid;
ROWID OBJECTID DEPENDID
AAAT9kAAMAAAdMVAAA 1 99
AAAT9kAAMAAAdMVAAB 2 0
If in session 1 you now run:
update test_table set dependId=100000 where objectid in (2);
You're updating the "second" row in the table. When session 2 runs:
update test_table set dependId=200000 where objectid in (2,1);
It reads the data block. Then tries to acquire locks on them in the order they're stored. So it looks at the first row (objectid = 1), asks "is the locked?" Finds the answer is no. And locks the row.
It then repeats this process for the second row. Which is locked by session 1. When querying v$lock
, you should see two entries requesting 'TX' locks in lmode = 6. One for each session:
select sid from v$lock
where type = 'TX'
and lmode = 6;
SID
75
60
So at this stage both sessions have one row locked. And session 2 is waiting for session 1.
In session 1 you now run:
update test_table set dependId=100000 where objectid in (1);
BOOOOM! Deadlock!
OK, but how can we be sure that this is due to the order rows are stored?
Using attribute clustering (a 12c feature), we can change the order rows are stored in the blocks, so objectid = 2 is "first":
alter table test_table
add clustering
by linear order ( dependId );
alter table test_table move;
select rowid, t.* from test_table t
order by rowid;
ROWID OBJECTID DEPENDID
AAAT9lAAMAAAdM7AAA 2 0
AAAT9lAAMAAAdM7AAB 1 99
Repeat the test. In session 1:
update test_table set dependId=100000 where objectid in (2);
So this has locked the "first" row. In session 2:
update test_table set dependId=200000 where objectid in (2,1);
This tries to lock the "first" row. But can't because session 1 has it locked. So at this point only session 1 holds any locks.
Check v$lock
to be sure:
select sid from v$lock
where type = 'TX'
and lmode = 6;
SID
60
And, sure enough, when you run the second update in session 1 it completes:
update test_table set dependId=100000 where objectid in (1);
NOTE
This doesn't mean that update
is guaranteed to lock rows in the order they're stored in the table blocks. Adding or removing indexes could affect this behaviour. As could changes between Oracle Database versions.
The key point is that update
has to lock rows in some order. It can't instantly acquire locks on all the rows it'll change.
So if you have two or more sessions with multiple updates, deadlock is possible. So you should start your transaction by locking all the rows you intend to change with select ... for update
.