One simple solution is to lock the table in share mode, to ensure no concurrent writes, before the largest update, with LOCK TABLE ... IN SHARE MODE.
Here are my two scripts if you want to reproduce:
The main one creates the table and runs the test case - /tmp/sql1.sql:
set echo on time on define off sqlprompt "SQL1> " linesize 69 pagesize 1000
set sqlformat ansiconsole
connect sys/oracle@//localhost/PDB1 as sysdba
grant dba to scott identified by tiger;
connect scott/tiger@//localhost/PDB1
exec begin execute immediate 'drop table T'; exception when others then null; end;
CREATE TABLE T (
SEQ NUMBER(10) constraint T_SEQ PRIMARY KEY,
VAL1 VARCHAR2(10),
VAL2 VARCHAR2(10)
);
insert into T select rownum , 0 , 0 from xmltable('1 to 5');
commit;
-- -------- start session 1
connect scott/tiger@//localhost/PDB1
select sys_context('userenv','sid') from dual;
variable val number
variable seq number;
exec :seq:=4; :val:=2;
UPDATE T SET VAL2 = :val WHERE SEQ = :seq;
-- -------- call session 2
host sql /nolog @/tmp/sql2.sql < /dev/null & :
host sleep 5
select session_id,lock_type,mode_held,mode_requested,lock_id1,lock_id2,blocking_others from dba_locks where lock_type in ('DML','Transaction','PL/SQL User Lock');
-- -------- continue session 1 while session 2 waits
exec :seq:=1; :val:=3;
UPDATE T SET VAL2 = :val WHERE SEQ = :seq;
host sleep 1
commit;
select * from T;
-- -------- end session 1
The second one is called in the main one to be run concurrently - /tmp/sql2.sql:
set echo on time on define off sqlprompt "SQL2> "
-- -------- start session 2 -------- --
host sleep 1
connect scott/tiger@//localhost/PDB1
select sys_context('userenv','sid') from dual;
variable val number
variable seq number;
exec :seq:=5; :val:=1;
/* TM lock solution */ lock table T in share mode;
UPDATE T SET VAL1 = :val WHERE SEQ < :seq;
commit;
select * from T;
-- -------- end session 2
Here is the run with the shared lock, where we see DML lock 'Share' blocked by 'Row-X' (which is the one automatically acquired by updates):
SQLcl: Release 18.4 Production on Wed Apr 17 09:32:04 2019
Copyright (c) 1982, 2019, Oracle. All rights reserved.
SQL>
SQL> set echo on time on define off sqlprompt "SQL1> " linesize 69 pagesize 1000
09:32:04 SQL1> set sqlformat ansiconsole
09:32:04 SQL1> connect sys/oracle@//localhost/PDB1 as sysdba
Connected.
09:32:05 SQL1>
09:32:05 SQL1> grant dba to scott identified by tiger;
Grant succeeded.
09:32:05 SQL1> connect scott/tiger@//localhost/PDB1
Connected.
09:32:08 SQL1>
09:32:08 SQL1> exec begin execute immediate 'drop table T'; exception when others then null; end;
PL/SQL procedure successfully completed.
09:32:09 SQL1> CREATE TABLE T (
2 SEQ NUMBER(10) constraint T_SEQ PRIMARY KEY,
3 VAL1 VARCHAR2(10),
4 VAL2 VARCHAR2(10)
5 );
Table created.
09:32:09 SQL1> insert into T select rownum , 0 , 0 from xmltable('1 to 5');
5 rows created.
09:32:09 SQL1> commit;
Commit complete.
09:32:09 SQL1> -- -------- start session 1
09:32:09 SQL1> connect scott/tiger@//localhost/PDB1
Connected.
09:32:09 SQL1>
09:32:09 SQL1> select sys_context('userenv','sid') from dual;
SYS_CONTEXT('USERENV','SID')
4479
09:32:09 SQL1> variable val number
09:32:09 SQL1> variable seq number;
09:32:09 SQL1> exec :seq:=4; :val:=2;
PL/SQL procedure successfully completed.
09:32:09 SQL1> UPDATE T SET VAL2 = :val WHERE SEQ = :seq;
1 row updated.
09:32:09 SQL1> -- -------- call session 2
09:32:09 SQL1> host sql /nolog @/tmp/sql2.sql < /dev/null & :
09:32:09 SQL1> host sleep 5
SQLcl: Release 18.4 Production on Wed Apr 17 09:32:10 2019
Copyright (c) 1982, 2019, Oracle. All rights reserved.
09:32:10 SQL2> -- -------- start session 2 -------- --
09:32:10 SQL2> host sleep 1
09:32:11 SQL2> connect scott/tiger@//localhost/PDB1
Connected.
09:32:11 SQL2> select sys_context('userenv','sid') from dual;
SYS_CONTEXT('USERENV','SID')
4478
09:32:12 SQL2> variable val number
09:32:12 SQL2> variable seq number;
09:32:12 SQL2> exec :seq:=5; :val:=1;
PL/SQL procedure successfully completed.
09:32:12 SQL2> /* TM lock solution */
09:32:12 SQL2> lock table T in share mode;
09:32:14 SQL1> select session_id,lock_type,mode_held,mode_requested,lock_id1,lock_id2,blocking_others from dba_locks where lock_type in ('DML','Transaction','PL/SQL User Lock');
SESSION_ID LOCK_TYPE MODE_HELD MODE_REQUESTED LOCK_ID1 LOCK_ID2 BLOCKING_OTHERS
4478 DML None Share 73192 0 Not Blocking
4479 DML Row-X (SX) None 73192 0 Blocking
4479 Transaction Exclusive None 655386 430384 Not Blocking
09:32:14 SQL1> -- -------- continue session 1 while session 2 waits
09:32:14 SQL1> exec :seq:=1; :val:=3;
PL/SQL procedure successfully completed.
09:32:17 SQL1> UPDATE T SET VAL2 = :val WHERE SEQ = :seq;
1 row updated.
09:32:17 SQL1> host sleep 1
09:32:18 SQL1> commit;
Lock succeeded.
Commit complete.
09:32:18 SQL2> UPDATE T SET VAL1 = :val WHERE SEQ < :seq;
09:32:18 SQL1> select * from T;
4 rows updated.
09:32:18 SQL2> commit;
Commit complete.
09:32:18 SQL2> select * from T;
SEQ VAL1 VAL2
1 1 3
2 1 0
3 1 0
4 1 2
5 0 0
09:32:18 SQL1> -- -------- end session 1
SEQ VAL1 VAL2
1 1 3
2 1 0
3 1 0
4 1 2
5 0 0
09:32:18 SQL2> -- -------- end session 2
09:32:18 SQL2>
Disconnected from Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.2.0.0.0
And same example without the shared lock, where we see Transaction exclusive lock (which is the one when an update encounters a row locked by another transaction) leading to deadlock:
SQLcl: Release 18.4 Production on Wed Apr 17 09:39:35 2019
Copyright (c) 1982, 2019, Oracle. All rights reserved.
SQL>
SQL> set echo on time on define off sqlprompt "SQL1> " linesize 69 pagesize 1000
09:39:35 SQL1> set sqlformat ansiconsole
09:39:35 SQL1> connect sys/oracle@//localhost/PDB1 as sysdba
Connected.
09:39:36 SQL1>
09:39:36 SQL1> grant dba to scott identified by tiger;
Grant succeeded.
09:39:36 SQL1> connect scott/tiger@//localhost/PDB1
Connected.
09:39:36 SQL1>
09:39:36 SQL1> exec begin execute immediate 'drop table T'; exception when others then null; end;
PL/SQL procedure successfully completed.
09:39:37 SQL1> CREATE TABLE T (
2 SEQ NUMBER(10) constraint T_SEQ PRIMARY KEY,
3 VAL1 VARCHAR2(10),
4 VAL2 VARCHAR2(10)
5 );
Table created.
09:39:37 SQL1> insert into T select rownum , 0 , 0 from xmltable('1 to 5');
5 rows created.
09:39:37 SQL1> commit;
Commit complete.
09:39:37 SQL1> -- -------- start session 1
09:39:37 SQL1> connect scott/tiger@//localhost/PDB1
Connected.
09:39:37 SQL1>
09:39:37 SQL1> select sys_context('userenv','sid') from dual;
SYS_CONTEXT('USERENV','SID')
4479
09:39:37 SQL1> variable val number
09:39:37 SQL1> variable seq number;
09:39:37 SQL1> exec :seq:=4; :val:=2;
PL/SQL procedure successfully completed.
09:39:37 SQL1> UPDATE T SET VAL2 = :val WHERE SEQ = :seq;
1 row updated.
09:39:37 SQL1> -- -------- call session 2
09:39:37 SQL1> host sql /nolog @/tmp/sql2.sql < /dev/null & :
09:39:37 SQL1> host sleep 5
SQLcl: Release 18.4 Production on Wed Apr 17 09:39:38 2019
Copyright (c) 1982, 2019, Oracle. All rights reserved.
09:39:38 SQL2> -- -------- start session 2 -------- --
09:39:38 SQL2> host sleep 1
09:39:39 SQL2> connect scott/tiger@//localhost/PDB1
Connected.
09:39:39 SQL2> select sys_context('userenv','sid') from dual;
SYS_CONTEXT('USERENV','SID')
4478
09:39:40 SQL2> variable val number
09:39:40 SQL2> variable seq number;
09:39:40 SQL2> exec :seq:=5; :val:=1;
PL/SQL procedure successfully completed.
09:39:40 SQL2> /* TM lock solution */
09:39:40 SQL2> --lock table T in share mode;
09:39:40 SQL2> UPDATE T SET VAL1 = :val WHERE SEQ < :seq;
09:39:42 SQL1> select session_id,lock_type,mode_held,mode_requested,lock_id1,lock_id2,blocking_others from dba_locks where lock_type in ('DML','Transaction','PL/SQL User Lock');
SESSION_ID LOCK_TYPE MODE_HELD MODE_REQUESTED LOCK_ID1 LOCK_ID2 BLOCKING_OTHERS
4478 Transaction None Exclusive 655368 430383 Not Blocking
4479 DML Row-X (SX) None 73194 0 Not Blocking
4478 DML Row-X (SX) None 73194 0 Not Blocking
4479 Transaction Exclusive None 655368 430383 Blocking
4478 Transaction Exclusive None 589838 281188 Not Blocking
09:39:46 SQL1> -- -------- continue session 1 while session 2 waits
09:39:46 SQL1> exec :seq:=1; :val:=3;
PL/SQL procedure successfully completed.
09:39:46 SQL1> UPDATE T SET VAL2 = :val WHERE SEQ = :seq;
1 row updated.
09:39:47 SQL1> host sleep 1
UPDATE T SET VAL1 = :val WHERE SEQ < :seq
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
09:39:47 SQL2> commit;
Commit complete.
09:39:47 SQL2> select * from T;
SEQ VAL1 VAL2
1 0 0
2 0 0
3 0 0
4 0 0
5 0 0
09:39:47 SQL2> -- -------- end session 2
09:39:47 SQL2>
Disconnected from Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.2.0.0.0
09:39:48 SQL1> commit;
Commit complete.
09:39:48 SQL1> select * from T;
SEQ VAL1 VAL2
1 0 3
2 0 0
3 0 0
4 0 2
5 0 0
09:39:48 SQL1> -- -------- end session 1
This share lock prevents all concurrent modifications, even some modifications on tables linked by referential integrity, so take care of the overall write activity on them. Another solution is to use a custom user lock with dbms_lock to serialize the two sets of updates.
Regards,
Franck.