Search notes:

DBMS_LOCK: Examples

Copy of adp-gmbh.ch: Synchronize sessions so that they starting an action at the same time

This example is copied from adp-gmbh.ch.
Three sessions are needed for this experiment.
Let's say, we want two sessions to do a task concurrently, that is, both tasks should start at the same time.
This is ideally solved with dbms_lock.
The following table will be filled by these two tasks:
create table lock_test (
   action varchar(10),
   when   date
);
In the first session, an exclusive lock (indicated by the x_mode flag) is requested:
declare
   v_lockhandle varchar2(200);
   v_result     number;
begin

   dbms_lock.allocate_unique('control_lock', v_lockhandle);

   v_result := dbms_lock.request(v_lockhandle, dbms_lock.x_mode);

   if v_result <> 0 then
      dbms_output.put_line(
             case
                when v_result=1 then 'Timeout'
                when v_result=2 then 'Deadlock'
                when v_result=3 then 'Parameter Error'
                when v_result=4 then 'Already owned'
                when v_result=5 then 'Illegal Lock Handle'
              end);
   end if;

end;
/
The following block is identical for the two other sessions. Similarly to the first session, a lock is requested, however, this time, the lock is a shared lock (indicated by the ss_mode flag).
According to the lock compatibility chart, a shared lock cannot be aquired if another session holds an exclusive lock. So, if a session executes the following block, it will stop execution on the request() call until the session holding the exclusive lock releases that lock.
Ok, the following block is started within two different sessions:
declare
   v_result     number;
   v_lockhandle varchar2(200);
begin

   dbms_lock.allocate_unique('control_lock', v_lockhandle);

   v_result := dbms_lock.request(v_lockhandle, dbms_lock.ss_mode);

   if v_result <> 0 then
      dbms_output.put_line(
           case
              when v_result=1 then 'Timeout'
              when v_result=2 then 'Deadlock'
              when v_result=3 then 'Parameter Error'
              when v_result=4 then 'Already owned'
              when v_result=5 then 'Illegal Lock Handle'
            end);
   end if;

   insert into lock_test values ('started', sysdate);
   dbms_lock.sleep(5);
   insert into lock_test values ('ended'  , sysdate);

   commit;

end;
/
Now, the first session can release the exclusive lock:
declare
   v_lockhandle varchar2(200);
   v_result     number;
begin

   dbms_lock.allocate_unique('control_lock', v_lockhandle);

   v_result := dbms_lock.release(v_lockhandle);

   if v_result <> 0 then 
      dbms_output.put_line(
           case 
              when v_result=1 then 'Timeout'
              when v_result=2 then 'Deadlock'
              when v_result=3 then 'Parameter Error'
              when v_result=4 then 'Already owned'
              when v_result=5 then 'Illegal Lock Handle'
           end);
   end if;
end;
/
As soon as the exclusive lock is released, both sessions that waited on aquiring the shared lock can resume their execution. This time, according to the lock compatibility chart, it is possible for two (or more) sessions to hold a shared lock. This, after all, is why it is called shared.
The two sessions will return after five seconds. Let's see what they've written into lock_test:
select to_char(when,'dd.mm.yyyy hh24:mi:ss'), action from lock_test order by when;
Both sessions started at the same time!

Locking one or all tables

create or replace package tq84_lock_test authid definer as -- {

   function  i_want_to_read (tab varchar2) return varchar2;
   function  i_want_to_write(tab varchar2) return varchar2;
   procedure i_am_done_with (tab varchar2);

   function  prevent_all_modifications     return varchar2;
   procedure enable_all_modifications;

end tq84_lock_test; -- }
/

create or replace package body tq84_lock_test as -- {

   -- Private functions {

   function  lockhandle(id varchar2) return varchar2 is -- {
      lckhnd varchar2(128);
   begin

      sys.dbms_lock.allocate_unique( -- {
         lockname        => id,
         lockhandle      => lckhnd
      ); -- }

      return lckhnd;

   end lockhandle; -- }

   function  lock_(id varchar2, mode_ integer) return boolean is -- {
   --
   -- Returns true if lock could be acquired, and
   -- false if lock is held by another session.
   --
      req_res integer;
   begin

      req_res := sys.dbms_lock.request ( -- {
         lockhandle  => lockhandle(id),
         lockmode    => mode_,
         timeout     => 0
      ); -- }

      if req_res  = 1 then return false; end if;

      if req_res not in (0, 4) then -- {
         raise_application_error(-20800, 'unpexected result when trying to lock ' || id || ': ' || req_res);
      end if; -- }

      return true;

   end  lock_; -- }

   procedure release_(id varchar2) is -- {
      req_res integer;
   begin
      req_res := dbms_lock.release(lockhandle(id));

      if req_res not in (0, 4) then raise_application_error(-20800, 'unexpected result ' || req_res || ' when trying to release ' || id); end if;
   end release_; -- }

   -- }

   function  i_want_to_read (tab varchar2) return varchar2 is -- {
      req_res integer;
   begin

      if not lock_('all', sys.dbms_lock.s_mode) then -- {
         return 'All tables are locked';
      end if; -- }

      if lock_(tab, sys.dbms_lock.s_mode) then -- {
         return 'OK, table ' || tab || ' is now locked in shared mode';
      end if; -- }

      return 'Could not acquire lock for table ' || tab;

   end i_want_to_read; -- }

   function  i_want_to_write(tab varchar2) return varchar2 is -- {
      req_res integer;
   begin

      if not lock_('all', sys.dbms_lock.s_mode) then -- {
         return 'All tables are locked';
      end if; -- }

      if lock_(tab, sys.dbms_lock.x_mode) then -- {
         return 'OK, table ' || tab || ' is now locked in exclusive mode';
      end if; -- }

      return 'Could not acquire lock for table ' || tab;

   end i_want_to_write; -- }

   procedure i_am_done_with(tab varchar2) is -- {
   begin

      release_( tab );
      release_('all');

   end i_am_done_with; -- }

   function  prevent_all_modifications     return varchar2 is -- {
   begin

      if lock_('all',sys.dbms_lock.x_mode) then return 'ALL tables are locked'; end if;
      return 'Cannot exclusively lock ALL tables';

   end prevent_all_modifications; -- }

   procedure enable_all_modifications is -- {
   begin
      release_('all');
   end enable_all_modifications; -- }

end tq84_lock_test; -- }
/
alter session set events 'immediate crash';

select sys_context('userenv', 'sid') from dual;

set serveroutput on

   -- T1

begin dbms_output.put_line(tq84_lock_test.i_want_to_read ('T1')); end;
/

begin dbms_output.put_line(tq84_lock_test.i_want_to_write('T1')); end;
/

begin tq84_lock_test.i_am_done_with ('T1'); end;
/

   -- T2

begin dbms_output.put_line(tq84_lock_test.i_want_to_read ('T2')); end;
/

begin dbms_output.put_line(tq84_lock_test.i_want_to_write('T2')); end;
/

begin tq84_lock_test.i_am_done_with ('T2'); end;
/
alter session set events 'immediate crash';

select sys_context('userenv', 'sid') from dual;

set serveroutput on

begin dbms_output.put_line(tq84_lock_test.prevent_all_modifications); end;
/

begin tq84_lock_test.enable_all_modifications; end;
/
select
  case lck.lmode
       when 4 then 'S'
       when 6 then 'X'
       else to_char(lck.lmode)
  end                          lm,
  round(lck.ctime/100)         since_s,
  ses.username,
  dla.name,
  ses.sid
FROM
  v$lock                  lck                                  join
  v$session               ses on lck.sid    = ses.sid          join
  sys.dbms_lock_allocated dla on dla.lockid = lck.id1
where
   1=1
-- lck.type   = 'UL'   and
-- dla.name   = 'T1'
;

The blue and green session

blue.sql

prompt
prompt The "blue" session
prompt ==================
prompt


variable lockhandle varchar2(100)

variable x_mode     number
exec :x_mode := dbms_lock.x_mode

exec dbms_lock.allocate_unique('tq84-lock', :lockhandle)
exec dbms_output.put_line ('lockhandle ' ||  :lockhandle || ' allocated');
select case dbms_lock.request(:lockhandle, :x_mode) 
       when 0 then 'Success'
       when 1 then 'Timeout'
       when 2 then 'Deadlock'
       when 3 then 'Parameter error'
       when 4 then 'Already own lock'
       when 5 then 'Illegal lock handle' end from dual;


prompt Go now to the green session and press enter
prompt then press enter here to commit the blue session
accept x prompt " "

commit;

prompt commit issued.
prompt The green session should still be blocked.
prompt Press enter here to terminate the blue
prompt session. This should unblock the green
prompt session.
accept x prompt " "


exit;

green.sql

prompt
prompt The "green" session
prompt ===================
prompt

prompt the Blue session has allocated a lock
prompt press enter to try to allocate the same lock
accept x prompt "in the green session as well."
prompt


variable lockhandle varchar2(100) 

variable x_mode     number
exec :x_mode := dbms_lock.x_mode

exec dbms_lock.allocate_unique('tq84-lock', :lockhandle)
exec dbms_output.put_line ('lockhandle ' ||  :lockhandle || ' allocated');
select case dbms_lock.request(:lockhandle, :x_mode) 
       when 0 then 'Success'
       when 1 then 'Timeout'
       when 2 then 'Deadlock'
       when 3 then 'Parameter error'
       when 4 then 'Already own lock'
       when 5 then 'Illegal lock handle' end from dual;

exit;

run.bat

@set   connection_string=rene/rene

@start session_blue.bat
@start session_green.bat

session_blue.bat

@rem echo %connection_string%

@color 1f

@sqlplus -S %connection_string% @blue

session_green.bat

@rem echo %connection_string%

@color 2f

@sqlplus -S %connection_string% @green

stop_sessions/blue.sql

declare

   lock_handle_stop    varchar2(100);
   lock_handle_running varchar2(100);

   success number;
   dummy   number;


begin
   -- We indicate that we're running
   dbms_lock.allocate_unique('running', lock_handle_running);
   success := dbms_lock.request(lock_handle_running, dbms_lock.s_mode, 0);



   dbms_lock.allocate_unique('stop-iteration', lock_handle_stop);

   loop

   --  We try to allocate a shared lock on lock_handle_stop.
   --
   --
   --  If another session already holds the lock exclusively,
   --  request will return 1 (that is: timeout).
   --
   --  We wait 0 seconds, so that we immediatly can
   --  exit the loop.


       success := dbms_lock.request(lock_handle_stop, dbms_lock.s_mode, 0.1);

       --  We also release the lock immediatly, so that we give
       --  another session the chance to lock it exclusively:
       dummy   := dbms_lock.release(lock_handle_stop);


       if success = 1 then -- timeout has occured
          exit;
       end if;

   --  Wait for 5 to 10 seconds
   --
   --  This would be the "critical part".
       dbms_lock.sleep(dbms_random.value(5,10));

   end loop;

   --         We're not really running anymore, so
   --         we release the according lock, so that
   --         the "green session" can enslock on this ure we're
   --         not running anymore by successfully
   --         requesting an exclusive lock
   dummy   := dbms_lock.release(lock_handle_running);

end;
/

exit

stop_sessions/green.sql

prompt
prompt press enter to stop the blue sessions
accept x prompt " "

declare

   lock_handle_stop      varchar2(100);
   lock_handle_running   varchar2(100);

   success number;
   dummy   number;


begin
   dbms_lock.allocate_unique('running'       , lock_handle_running);
   dbms_lock.allocate_unique('stop-iteration', lock_handle_stop   );


   -- We try to request an exclusive lock.
   -- As soon as we got it, we know that the other jobs are not
   -- in the "critical part"
   --
   -- We try 60 seconds only
   success := dbms_lock.request(lock_handle_stop, dbms_lock.x_mode, 60);

   if success = 1 then
      dbms_output.put_line('Timeout, could not stop other jobs');
   else

      success := dbms_lock.request(lock_handle_stop, dbms_lock.x_mode, 60);

   -- Now that we have exclusively gotten the running lock (which means that
   -- the "critical part" can only be left, but not entered anymore, we
   -- also want the running lock exclusively which we get as soon as all
   --"blue" jobs have indicated they're leaving the processing.
      success := dbms_lock.request(lock_handle_running, dbms_lock.x_mode, 60);

      dbms_output.put_line('Other jobs should now be terminated, success: ' || success);
   end if;

   dummy   := dbms_lock.release(lock_handle_stop);
end;
/

exit

stop_sessions/run.bat

@set   connection_string=rene/rene

@rem   --------------------------------
@rem   start four sessions

@start session_blue.bat
@start session_blue.bat
@start session_blue.bat
@start session_blue.bat



@rem   --------------------------------
@rem   start another session to stop the
@rem   executions of the other four 
@rem   sessions:

@start session_green.bat

stop_sessions/session_blue.bat

@color 1f

@sqlplus -S %connection_string% @blue

stop_sessions/session_green.bat

@color 2f

@sqlplus -S %connection_string% @green

See also

dbms_lock
An example of how synchronizing sessions might be used is found in Why is dynamic SQL bad?.

Index

Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 8 attempt to write a readonly database in /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php:51 Stack trace: #0 /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php(51): PDOStatement->execute(Array) #1 /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php(66): id_of(Object(PDO), 'uri', '/notes/developm...') #2 /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php(30): insert_webrequest_('/notes/developm...', 1758207332, '216.73.216.150', 'Mozilla/5.0 App...', NULL) #3 /home/httpd/vhosts/renenyffenegger.ch/httpsdocs/notes/development/databases/Oracle/installed/packages/dbms/lock/examples/index(610): insert_webrequest() #4 {main} thrown in /home/httpd/vhosts/renenyffenegger.ch/php/web-request-database.php on line 51