Tuesday, October 12, 2021

Oracle sessiontimezone Format Changed After Calling dbms_scheduler/dbms_job Subprograms

dbms_scheduler/dbms_job subprograms implicitly change sessiontimezone from Named TZ (TZR) to Offset TZ (TZH:TZM) format.
As a side-effect, sessiontimezone is no more DST (Daylight saving time) aware.
(see Blog: Oracle Datetime (1) - Concepts)

This Blog will provide standalone tests and workaround.

Note 1: Tested in Oracle 19c (19.11).
Note 2: The behaviour has long been observed by other people in Oracle dbms_scheduler/dbms_job applications.


1. dbms_scheduler.enable



-----=========== dbms_scheduler.enable change time_zone ===========----- 
 
declare
  l_job_name         varchar2(100) := 'TEST_JOB_1';   
  l_time_zone_orig   varchar2(40);
  l_time_zone        varchar2(40);
begin
  execute immediate q'[alter session set time_zone = 'Europe/Paris']';
  l_time_zone_orig := sessiontimezone;
  dbms_output.put_line('----- 1 sessiontimezone = '|| sessiontimezone);
  dbms_scheduler.create_job(
    job_name   => l_job_name
   ,job_type   => 'PLSQL_BLOCK'
   ,job_action =>
     'begin
        dbms_session.sleep(300);
      end;'
   ,start_date      => systimestamp
   ,repeat_interval => 'systimestamp'   -- without repeat_interval, not reproducible
   ,enabled         => false
   ,auto_drop       => false);
  
  dbms_output.put_line('----- 2 sessiontimezone = '|| sessiontimezone);
  dbms_scheduler.enable(name  => l_job_name);
 
  l_time_zone := sessiontimezone;
  -- Workaround
  if (l_time_zone != l_time_zone_orig) then
    dbms_output.put_line('----- 3 sessiontimezone = '|| sessiontimezone); 
    dbms_output.put_line('--******* Changed after dbms_scheduler.enable. Restore to Original: '|| l_time_zone_orig); 
    execute immediate 'alter session set time_zone = ''' || l_time_zone_orig || '''';
  end if;
  dbms_output.put_line('----- 4 sessiontimezone = '|| sessiontimezone);
 
  dbms_scheduler.stop_job (l_job_name, force => true, commit_semantics =>'ABSORB_ERRORS');
  dbms_scheduler.disable(name  => l_job_name);
  dbms_scheduler.drop_job(l_job_name, force => true, commit_semantics =>'ABSORB_ERRORS');
  dbms_output.put_line('----- 5 sessiontimezone = '|| sessiontimezone);
end;
/
 
-----=========== Test Output ===========----- 
 
----- 1 sessiontimezone = Europe/Paris
----- 2 sessiontimezone = Europe/Paris
----- 3 sessiontimezone = +02:00
--******* Changed after dbms_scheduler.enable. Restore to Original: Europe/Paris
----- 4 sessiontimezone = Europe/Paris
----- 5 sessiontimezone = Europe/Paris


2. dbms_scheduler.set_attribute



-----=========== dbms_scheduler.set_attribute change time_zone ===========----- 
 
declare
  l_job_name         varchar2(100) := 'TEST_JOB_2'; 
begin
  execute immediate q'[alter session set time_zone = 'Europe/Paris']';
  dbms_output.put_line('----- 1 sessiontimezone = '|| sessiontimezone);
  dbms_scheduler.create_job(
    job_name   => l_job_name
   ,job_type   => 'PLSQL_BLOCK'
   ,job_action => 'BEGIN dbms_session.sleep(30); END;'
   ,start_date      => systimestamp
   ,repeat_interval => 'systimestamp'   -- without repeat_interval, not reproducible
   ,enabled         => true
   ,auto_drop       => false);
  
  dbms_output.put_line('----- 2 sessiontimezone = '|| sessiontimezone);
end;
/
 
declare
  l_job_name         varchar2(100) := 'TEST_JOB_2';    --'AVQ$AAA_BGP_911_1';
  l_time_zone_orig   varchar2(40);
  l_time_zone        varchar2(40);
BEGIN
  dbms_session.sleep(5);
  l_time_zone_orig := sessiontimezone;
  dbms_scheduler.stop_job (l_job_name, force => true, commit_semantics =>'ABSORB_ERRORS');
 
  dbms_output.put_line('----- 3 sessiontimezone = '|| sessiontimezone);
  dbms_scheduler.set_attribute(
    name      => l_job_name
   ,attribute => 'job_action'
   ,value     => 'BEGIN dbms_session.sleep(60); END;'
  );
 
  l_time_zone := sessiontimezone;
  -- Workaround
  if (l_time_zone != l_time_zone_orig) then
    dbms_output.put_line('----- 4 sessiontimezone = '|| sessiontimezone); 
    dbms_output.put_line('--******* Changed after dbms_scheduler.set_attribute. Restore to Original: '|| l_time_zone_orig); 
    execute immediate 'alter session set time_zone = ''' || l_time_zone_orig || '''';
  end if;
 
  dbms_output.put_line('----- 5 sessiontimezone = '|| sessiontimezone);
  dbms_scheduler.stop_job (l_job_name, force => true, commit_semantics =>'ABSORB_ERRORS');
  dbms_scheduler.disable(name  => l_job_name);
  dbms_scheduler.drop_job(l_job_name, force => true, commit_semantics =>'ABSORB_ERRORS');
  dbms_output.put_line('----- 6 sessiontimezone = '|| sessiontimezone); 
end;
/
 
-----=========== Test Output ===========----- 
 
----- 1 sessiontimezone = Europe/Paris
----- 2 sessiontimezone = Europe/Paris
----- 3 sessiontimezone = Europe/Paris
----- 4 sessiontimezone = +02:00
--******* Changed after dbms_scheduler.set_attribute. Restore to Original: Europe/Paris
----- 5 sessiontimezone = Europe/Paris
----- 6 sessiontimezone = Europe/Paris


3. dbms_job.change



-----=========== dbms_job.change change time_zone ===========----- 
 
declare
  l_job_id pls_integer;
  l_time_zone_orig   varchar2(40);
  l_time_zone        varchar2(40);
begin
  execute immediate q'[alter session set time_zone = 'Europe/Paris']';
  l_time_zone_orig := sessiontimezone;
  dbms_output.put_line('----- 1 sessiontimezone = '|| sessiontimezone);
  dbms_job.submit(
    job       => l_job_id,
    what      => 'BEGIN dbms_session.sleep(30); END;',
    next_date => sysdate,
    interval  => 'sysdate+1/1440'  
   );
  commit;
  dbms_output.put_line('--******* l_job_id=' || l_job_id);
 
  --exception when others then dbms_output.put_line('error l_job_id =' || l_job_id); raise;
 
  dbms_output.put_line('----- 2 sessiontimezone = '|| sessiontimezone);
  dbms_job.change (
   job       =>  l_job_id,
   what      =>  'BEGIN dbms_session.sleep(60); END;',
   next_date => sysdate,
   interval  => 'sysdate+1/1440');
  commit;
 
  l_time_zone := sessiontimezone;
  -- Workaround
  if (l_time_zone != l_time_zone_orig) then
    dbms_output.put_line('----- 3 sessiontimezone = '|| sessiontimezone); 
    dbms_output.put_line('--******* Changed after dbms_job.change. Restore to Original: '|| l_time_zone_orig); 
    execute immediate 'alter session set time_zone = ''' || l_time_zone_orig || '''';
  end if;
 
  dbms_output.put_line('----- 4 sessiontimezone = '|| sessiontimezone);
 
  dbms_job.remove(l_job_id);
  commit;
  dbms_output.put_line('----- 5 sessiontimezone = '|| sessiontimezone);
end;      
/
 
-----=========== Test Output ===========----- 
 
----- 1 sessiontimezone = Europe/Paris
--******* l_job_id=86009
----- 2 sessiontimezone = Europe/Paris
----- 3 sessiontimezone = +02:00
--******* Changed after dbms_job.change. Restore to Original: Europe/Paris
----- 4 sessiontimezone = Europe/Paris
----- 5 sessiontimezone = Europe/Paris


4. Discussions


In all above three tests, we give a workaround to restore the original sessiontimezone.

We also tried to toggle dbms_scheduler attribute: 'default_timezone', there is no effect.

begin
  dbms_scheduler.set_scheduler_attribute(
    attribute => 'default_timezone',
    value     => 'Europe/Paris');
end;
/
 
select dbms_scheduler.stime from dual;
 
--   06-OCT-21 11.34.36.520960000 AM EUROPE/PARIS
 
begin
  dbms_scheduler.set_scheduler_attribute(
    attribute => 'default_timezone',
    value     => '02:00');
end;
/
 
select dbms_scheduler.stime from dual;
 
--   06-OCT-21 11.40.06.230467000 AM +02:00
It looks like that sessiontimezone is gotten by Oracle subroutine "pesstz", and dbtimezone by "pesdbtz".

It seems that session parameter modifications, for example:

  alter session set time_zone = 'CET';
  
  alter session set sql_trace=true;
  alter session set sql_trace=false;
are related to "kzctxhset".

MOS Note "DBMS_SCHEDULER or DBMS_JOB And DST / Timezones Explained (Doc ID 467722.1)" has a detail description of Job starting time in connection with Timezone setting in format of Named TZ (TZR) and Offset TZ (TZH:TZM).

Friday, July 9, 2021

Oracle Program and Table dc_objects "row cache lock"

In previous Blog: One "row cache lock" Test Case, we discussed dc_users "row cache lock".

In this Blog, we will look two types of dc_objects "row cache lock", one is Plsql program, another is table/partitions.

Note: Tested in Oracle 19c.


1. Test Setup


At first, we create two list partitioned tables, and Plsql procedures to add and drop partitions.

drop table test_tab_1 purge;
drop table test_tab_2 purge;

create table test_tab_1 (part_id number, val number) partition by list (part_id) (partition p_0 values (0));

create table test_tab_2 (part_id number, val number) partition by list (part_id) (partition p_0 values (0));

create or replace procedure add_part (p_tab_name varchar2, p_part_id number) as
  l_part_name  varchar2(10);
begin
  select decode(sign(p_part_id) , 1, 'P_', 'N_')||abs(p_part_id) into l_part_name from dual;
  execute immediate 'alter table '||p_tab_name||' add partition '||l_part_name||' values('||p_part_id||')';
  execute immediate 'insert into '||p_tab_name||' values ('||p_part_id||','||p_part_id||')';
  commit;
end;
/

create or replace procedure drop_part(p_tab_name varchar2, p_part_id number) as
  l_part_name  varchar2(10);
begin
  select decode(sign(p_part_id) , 1, 'P_', 'N_')||abs(p_part_id) into l_part_name from dual;
	execute immediate 'alter table '||p_tab_name||' drop partition '||l_part_name;
end;
/

-- exec add_part('TEST_TAB_1', 1);
-- exec drop_part('TEST_TAB_1', 1);
And one procedure to loop add and drop partitions:

create or replace procedure add_drop_part_loop(p_tab_name varchar2, p_cnt number, p_dur number, p_sign number := 1) as
	l_start   number := dbms_utility.get_time;
	l_run_cnt number := 0;
begin
  -- cleanup
	for c in (select * from dba_tab_partitions 
	           where table_name = p_tab_name and partition_name != 'P_0' and partition_name like (decode(p_sign, 1, 'P', 'N')||'_%')) 
	loop
	  drop_part(c.table_name, p_sign*substr(c.partition_name, 3));
	end loop;
	
  l_start  := dbms_utility.get_time;
  while (dbms_utility.get_time - l_start) < p_dur*100 loop
	  for i in 1..p_cnt loop
	    add_part(p_tab_name, p_sign*i);
	  end loop;
	  
	  for i in 1..p_cnt loop
	    drop_part(p_tab_name, p_sign*i);
	  end loop;
	  
	  l_run_cnt := l_run_cnt + 1;
	end loop;
	dbms_output.put_line('Table='||p_tab_name||', run_cnt='||l_run_cnt||', elapsed='||round((dbms_utility.get_time-l_start)/100));
end;
/


2. Program dc_objects "row cache lock"


We create a small Plsql program that depends on both above created tables.

create or replace procedure test_tab_bind as
  l_cnt number;
begin
  select count(*) into l_cnt from TEST_TAB_1, TEST_TAB_2; 
end;
/
Open two Sqlplus sessions (SID-1: 186, SID-2: 550), and run following test code for a duration of 300 seconds.
     In SID-1, we add_drop 'TEST_TAB_1';
     in SID-2, we add_drop 'TEST_TAB_2'.

-- SID-1: 186, add_drop 'TEST_TAB_1'
select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event 
where event = 'row cache lock' and sid = (select sid from v$mystat where rownum=1);

exec add_drop_part_loop('TEST_TAB_1', 100, 300);

select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event 
where event = 'row cache lock' and sid = (select sid from v$mystat where rownum=1);


--SID-2: 550, add_drop 'TEST_TAB_2'
select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event 
where event = 'row cache lock' and sid = (select sid from v$mystat where rownum=1);

exec add_drop_part_loop('TEST_TAB_2', 100, 300);

select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event 
where event = 'row cache lock' and sid = (select sid from v$mystat where rownum=1);
Here the test output:

-- SID-1: 186
select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event ...

  no rows selected

exec add_drop_part_loop('TEST_TAB_1', 100, 300);

  Table=TEST_TAB_1, run_cnt=79, elapsed=300

select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event ...   

  SID EVENT          TOTAL_WAITS TIME_WAITED AVERAGE_WAIT   MAX_WAIT
  --- -------------- ----------- ----------- ------------ ----------
  186 row cache lock        4143        3509          .85          3


--SID-2: 550
select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event ...

  no rows selected

exec add_drop_part_loop('TEST_TAB_2', 100, 300);

  Table=TEST_TAB_2, run_cnt=76, elapsed=300

select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event ...

  SID EVENT          TOTAL_WAITS TIME_WAITED AVERAGE_WAIT   MAX_WAIT
  --- -------------- ----------- ----------- ------------ ----------
  550 row cache lock        4142        3805          .92          3
We can see that there are about 4000 'row cache lock' concurrency waits in each session.

During the test, we can run query below to show the contention on 'dc_objects'
(cache_id: P1=8, Mode held: P2=0 (null), Mode request : P3=5 (exclusive))
(see MOS Docu: WAITEVENT: "row cache lock" Reference Note (Doc ID 34609.1)):

select chain_signature, sid, pid, osid, blocker_sid, blocker_is_valid, p1, p2, p3 from v$wait_chains;

  CHAIN_SIGNATURE    SID  PID OSID   BLOCKER_SID BLOCK   P1   P2   P3
  ---------------- ----- ---- ------ ----------- ----- ---- ---- ----
  'row cache lock'   186   49 11421          550 TRUE     8    0    5
  <not in a wait>    550   57 11575              FALSE
If we check DIAG dia0 or mmnl trace files, we can see the blocked session, blocker session, and the requested row cache parent address (po 0x9c79f958) in mode X. (we will give a further look in Section 4: dc_objects Internals).

--testdb_dia0_10885_base_1.trc
--testdb_mmnl_10915.trc

*** 2021-06-25T11:37:49.281348+02:00
kqrhngc ph1: po 0x9c79f958 req X from session 0xb8d1d508 lock 0x9cbfb5e8
kqrhngc ph1: blocker #1 owner  mode X session 0xb8990bb8 lock 0x9d32f7e0
With following query, we can find the contention dc_object: TEST_TAB_BIND (see Blog: Oracle ROWCACHE Views and Contents ):

select to_number(ltrim((substr(key, 7, 2)||substr(key, 5, 2)||substr(key, 3, 2)||substr(key, 1, 2)), '0'), 'XXXX') user_id,
       (select username from dba_users where user_id = 
          to_number(ltrim((substr(key, 7, 2)||substr(key, 5, 2)||substr(key, 3, 2)||substr(key, 1, 2)), '0'), 'XXXX')) user_name,
       dump_hex2str(rtrim(substr(key, 13), '0')) dc_object_name, 
       to_number(trim(both '0' from substr(key, 11, 2)||substr(key, 9, 2)), 'XXXX') key_len,
       indx, hash, address, cache#, cache_name, existent, lock_mode, lock_request, txn, saddr --,v.* 
from v$rowcache_parent v
where cache_name in ('dc_objects') and address like upper('%9c79f958');

  USER_ID USER_NAME  DC_OBJECT_NAME  KEY_LEN  INDX  HASH ADDRESS  CACHE# CACHE_NAME E LOCK_MODE LOCK_REQUEST TXN      SADDR
  ------- ---------- --------------- ------- ----- ----- -------- ------ ---------- - --------- ------------ -------- --------
       49 K          TEST_TAB_BIND        13  7196  5950 9C79F958      8 dc_objects Y         0            5 AE97BC88 B8D1D508
       49 K          TEST_TAB_BIND        13  7197  5950 9C79F958      8 dc_objects Y         5            0 AE97BC88 B8990BB8
       49 K          TEST_TAB_BIND        13 11549 27359 9C79F958      8 dc_objects Y         5            0 AE92EF30 B8D1D508
We can also list the involved sessions and (recursive) transactions:
(Note:
   gv$transaction is defined on x$ktcxb with filter: bitand (ksspaflg, 1) != 0 and bitand (ktcxbflg, 2) != 0.
   We use x$ktcxb since they are recursive transactions, which are filtered out in gv$transaction)

select sid, dc_object_name, r.indx, hash, address, cache#, cache_name, existent, lock_mode, lock_request, 
       txn, r.saddr, s.blocking_session --, s.*, t.* 
from (
  select to_number(ltrim((substr(key, 7, 2)||substr(key, 5, 2)||substr(key, 3, 2)||substr(key, 1, 2)), '0'), 'XXXX') user_id,
         (select username from dba_users where user_id = 
            to_number(ltrim((substr(key, 7, 2)||substr(key, 5, 2)||substr(key, 3, 2)||substr(key, 1, 2)), '0'), 'XXXX')) user_name,
         dump_hex2str(rtrim(substr(key, 13), '0')) dc_object_name, 
         to_number(trim(both '0' from substr(key, 11, 2)||substr(key, 9, 2)), 'XXXX') key_len, v.* 
  from v$rowcache_parent v
  where cache_name in ('dc_objects') and address like upper('%9c79f958')
  order by key) r, gv$session s, x$ktcxb t
where r.saddr = s.saddr(+) and r.txn = t.ktcxbxba(+);

  SID DC_OBJECT_NAME INDX  HASH  ADDRESS  CACHE# CACHE_NAME E LOCK_MODE LOCK_REQUEST TXN      SADDR    BLOCKING_SESSION
  ---- ------------- ----- ----- -------- ------ ---------- - --------- ------------ -------- -------- ----------------
   186 TEST_TAB_BIND  7291  5950 9C79F958      8 dc_objects Y         0            5 AEAB4380 B8990BB8              550
   186 TEST_TAB_BIND 11641 27359 9C79F958      8 dc_objects Y         5            0 AE7F2248 B8990BB8              550
   550 TEST_TAB_BIND  7292  5950 9C79F958      8 dc_objects Y         5            0 AEAB4380 B8D1D508
We can see that the contention of dc_objects 'row cache lock' is on 'TEST_TAB_BIND' because it binds TEST_TAB_1 and TEST_TAB_2 together.

If we invalidate 'TEST_TAB_BIND' by "dbms_utility.invalidate" and repeat above test, no more 'row cache lock' contention can be observed (Probably this can be used as a quick workaround to fix such contention).

select object_name, object_id, status from dba_objects where object_name = 'TEST_TAB_BIND';

  OBJECT_NAME    OBJECT_ID  STATUS
  ------------- ---------- -------
  TEST_TAB_BIND    4509953  VALID

exec sys.dbms_utility.invalidate(p_object_id =>4509953);  

--to validate:
--alter procedure TEST_TAB_BIND compile;

select object_name, object_id, status from dba_objects where object_name = 'TEST_TAB_BIND';

  OBJECT_NAME    OBJECT_ID  STATUS
  ------------- ---------- --------
  TEST_TAB_BIND    4509953  INVALID
In contrast to "dbms_utility.invalidate", if we created an INVALID Plsql program as follows and repeat above test, there still exists the same 'row cache lock' contention.

create or replace procedure test_tab_bind as
  l_cnt number;
begin
  -- xxxx_cnt is wrong name
  select count(*) into xxxx_cnt from TEST_TAB_1, TEST_TAB_2;  
end;
/

  Warning: Procedure created with compilation errors.
  
  5/24     PLS-00201: identifier 'XXXX_CNT' must be declared
  5/33     PL/SQL: ORA-00904: : invalid identifier

select object_name, object_id, status from dba_objects where object_name = 'TEST_TAB_BIND';

  OBJECT_NAME    OBJECT_ID  STATUS
  ------------- ---------- --------
  TEST_TAB_BIND    4509953  INVALID
After test, we re-create our VALID Plsql program:

create or replace procedure test_tab_bind as
  l_cnt number;
begin
  select count(*) into l_cnt from TEST_TAB_1, TEST_TAB_2; 
end;
/

select object_name, object_id, status from dba_objects where object_name = 'TEST_TAB_BIND';

  OBJECT_NAME    OBJECT_ID  STATUS
  ------------- ---------- -------
  TEST_TAB_BIND    4509953  VALID


3. Table and Partition dc_objects "row cache lock"


In last section, we demonstrated dc_objects "row cache lock" on Plsql program.
In this section, we will look dc_objects "row cache lock" on table/partition.

In two above Sqlplus sessions (SID-1: 186, SID-2: 550), run following test code for a duration of 300 seconds.
     In SID-1, we add_drop 'TEST_TAB_1' partitions prefixed by 'P_';
     in SID-2, we add_drop 'TEST_TAB_1' partitions prefixed by 'N_'.

-- SID-1: 186, add_drop 'TEST_TAB_1' partitions prefixed by 'P_'
select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event 
where event = 'row cache lock' and sid = (select sid from v$mystat where rownum=1);

-- Create/Drop partitions with name prefixed 'P_'
exec add_drop_part_loop('TEST_TAB_1', 100, 300, p_sign=>1);

select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event 
where event = 'row cache lock' and sid = (select sid from v$mystat where rownum=1);
       

-- SID-2: 550, add_drop 'TEST_TAB_1' partitions prefixed by 'N_'.    
select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event 
where event = 'row cache lock' and sid = (select sid from v$mystat where rownum=1);

-- Create/Drop partitions with name prefixed 'N_'
exec add_drop_part_loop('TEST_TAB_1', 100, 300, p_sign=>-1);

select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event 
where event = 'row cache lock' and sid = (select sid from v$mystat where rownum=1);
Here the test output. It shows that each session has about 500 'row cache lock' concurrency waits (5780-5217=563, 5751-5218=533).

-- SID-1: 186
select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event ...

  SID EVENT          TOTAL_WAITS TIME_WAITED AVERAGE_WAIT   MAX_WAIT
  --- -------------- ----------- ----------- ------------ ----------
  186 row cache lock        5217        5563         1.07          4

exec add_drop_part_loop('TEST_TAB_1', 100, 300, p_sign=>1);

  Table=TEST_TAB_1, run_cnt=23, elapsed=300

select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event ...

  SID EVENT          TOTAL_WAITS TIME_WAITED AVERAGE_WAIT   MAX_WAIT
  --- -------------- ----------- ----------- ------------ ----------
  186 row cache lock        5780        7246         1.25          7
       
   
-- SID-2: 500           
select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event ...

  SID EVENT          TOTAL_WAITS TIME_WAITED AVERAGE_WAIT   MAX_WAIT
  --- -------------- ----------- ----------- ------------ ----------
  550 row cache lock        5218        5804         1.11          4

exec add_drop_part_loop('TEST_TAB_1', 100, 300, p_sign=>-1);

  Table=TEST_TAB_1, run_cnt=22, elapsed=304
   
select sid, event, total_waits, time_waited, average_wait, max_wait from v$session_event ...

  SID EVENT          TOTAL_WAITS TIME_WAITED AVERAGE_WAIT   MAX_WAIT
  --- -------------- ----------- ----------- ------------ ----------
  550 row cache lock        5751        7334         1.28          6
During the test, we run query below to show the contention on 'dc_objects'
(cache_id: P1=8, Mode held: P2=0 (null), Mode request : P3=3 (share mode)).

Comparing to above P3=5 (exclusive) in case of dc_object Plsql program, dc_object table/partition request is blocked with P3=3 (share mode). (we will give a further look in Section 4: dc_objects Internals).

select chain_signature, sid, pid, osid, blocker_sid, blocker_is_valid, p1, p2, p3 from v$wait_chains;

  CHAIN_SIGNATURE                   SID PID OSID   BLOCKER_SID BLOCK  P1  P2  P3
  --------------------------------- --- --- ------ ----------- ----- --- --- ---
  <not in a wait><='row cache lock' 550  57 11575          186 TRUE    8   0   3
  <not in a wait><='row cache lock' 186  49 11421              FALSE
If we check DIAG dia0 or mmnl trace files, we can see the blocked session, blocker session. The blocked session requested row cache parent address (po 0x9c971fa8) in mode S, and blocked by mode X, held by blocker session.
      
--testdb_dia0_10885_base_1.trc
--testdb_mmnl_10915.trc

*** 2021-06-25T12:07:10.296235+02:00
kqrhngc ph1: po 0x9c971fa8 req S from session 0xb8990bb8 lock 0x9cfb6d50
kqrhngc ph1: blocker #1 owner  mode X session 0xb8d1d508 lock 0x9cbfd7b8
Similar to above Plsql program, we can also list the sessions and (recursive) transactions with po 0x9c971fa8:

select sid, dc_object_name, r.indx, hash, address, cache#, cache_name, existent, lock_mode, lock_request, 
       txn, r.saddr, s.blocking_session --, s.*, t.* 
from (
  select to_number(ltrim((substr(key, 7, 2)||substr(key, 5, 2)||substr(key, 3, 2)||substr(key, 1, 2)), '0'), 'XXXX') user_id,
         (select username from dba_users where user_id = 
            to_number(ltrim((substr(key, 7, 2)||substr(key, 5, 2)||substr(key, 3, 2)||substr(key, 1, 2)), '0'), 'XXXX')) user_name,
         dump_hex2str(rtrim(substr(key, 13), '0')) dc_object_name, 
         to_number(trim(both '0' from substr(key, 11, 2)||substr(key, 9, 2)), 'XXXX') key_len, v.* 
  from v$rowcache_parent v
  where cache_name in ('dc_objects') and address like upper('%9c971fa8')
  order by key) r, gv$session s, x$ktcxb t
where r.saddr = s.saddr(+) and r.txn = t.ktcxbxba(+);

  SID DC_OBJECT_NAME  INDX  HASH ADDRESS  CACHE# CACHE_NAME E LOCK_MODE LOCK_REQUEST TXN      SADDR    BLOCKING_SESSION
  --- --------------- ---- ----- -------- ------ ---------- - --------- ------------ -------- -------- ----------------
  186 TEST_TAB_1      5267 19145 9C971FA8      8 dc_objects Y         0            3 AEB04B20 B8990BB8              550
  550 TEST_TAB_1      8110 22657 9C971FA8      8 dc_objects Y         5            0 AEB04B20 B8D1D508
  186 TEST_TAB_1      8109 22657 9C971FA8      8 dc_objects Y         0            3 AEB04B20 B8990BB8              550
  550 TEST_TAB_1      5268 19145 9C971FA8      8 dc_objects Y         5            0 AEB04B20 B8D1D508


4. dc_objects Internals


To further understand 'row cache lock' contentions, we will make a blocking test with breakpoint, trace session activities, and dump Row Cache Data.


4.1 Blocking Test


At first, add one partition into 'TEST_TAB_1', and one into 'TEST_TAB_2':

exec add_part('TEST_TAB_1', 1);
exec add_part('TEST_TAB_2', 1);
Continue with two above Sqlplus sessions (SID-1: 186, SID-2: 550).

In SID-1 (ospid: 11421), set a breakpoint at retq of kqrpre1 with condition of above Plsql program dc_object (TEST_TAB_BIND: po 0x9c79f958):

gdb -p 11421

# break *kqrpre1 if $rdi==0x8 && $rcx==0x5
# <+3564>:	retq  
break *kqrpre1+3564 if $rdx==0x9c79f958
In SID-1, drop one partition of 'TEST_TAB_1':

-- SID-1
exec drop_part('TEST_TAB_1', 1);
It stopped with following frame info:

(gdb) c
Continuing.

Breakpoint 1, 0x00000000037ac6bc in kqrpre1 ()

(gdb) display /x $rdx
1: /x $rdx = 0x9c79f958

(gdb) bt 6
#0  0x00000000037ac6bc in kqrpre1 ()
#1  0x00000000032e95d6 in kqlidp0_int ()
#2  0x00000000032e7c19 in kqlidp0 ()
#3  0x0000000002631424 in atbFMdrop ()
#4  0x000000000261976a in atbdrv ()
#5  0x00000000127b19b0 in opiexe ()
(More stack frames follow...)
In SID-2, drop one partition of 'TEST_TAB_2':

-- SID-2
exec drop_part('TEST_TAB_2', 1);
Here the output of v$wait_chains (Mode request: P3=5 (exclusive)):

select chain_signature, sid, pid, osid, blocker_sid, blocker_is_valid, p1, p2, p3 from v$wait_chains;

  CHAIN_SIGNATURE                   SID PID OSID  BLOCKER_SID BLOCK  P1  P2  P3
  --------------------------------- --- --- ----- ----------- ----- --- --- ---
  <not in a wait><='row cache lock' 550  57 11575         186 TRUE    8   0   5
  <not in a wait><='row cache lock' 186  49 11421             FALSE
Some new lines appear in dia0 and mmnl trace files:

--testdb_dia0_10885_base_1.trc
--testdb_mmnl_10915.trc

*** 2021-06-25T11:37:49.281348+02:00
kqrhngc ph1: po 0x9c79f958 req X from session 0xb8d1d508 lock 0x9cbfb5e8
kqrhngc ph1: blocker #1 owner  mode X session 0xb8990bb8 lock 0x9d32f7e0
We can also list the contention details with the sessions and (recursive) transactions on po 0x9c79f958:

select sid, dc_object_name, r.indx, hash, address, cache#, cache_name, existent, lock_mode, lock_request, 
       txn, r.saddr, s.blocking_session --, s.*, t.* 
from (
  select to_number(ltrim((substr(key, 7, 2)||substr(key, 5, 2)||substr(key, 3, 2)||substr(key, 1, 2)), '0'), 'XXXX') user_id,
         (select username from dba_users where user_id = 
            to_number(ltrim((substr(key, 7, 2)||substr(key, 5, 2)||substr(key, 3, 2)||substr(key, 1, 2)), '0'), 'XXXX')) user_name,
         dump_hex2str(rtrim(substr(key, 13), '0')) dc_object_name, 
         to_number(trim(both '0' from substr(key, 11, 2)||substr(key, 9, 2)), 'XXXX') key_len, v.* 
  from v$rowcache_parent v
  where cache_name in ('dc_objects') and address like upper('%9c79f958')
  order by key) r, gv$session s, x$ktcxb t
where r.saddr = s.saddr(+) and r.txn = t.ktcxbxba(+);

  SID DC_OBJECT_NAME  INDX  HASH ADDRESS  CACHE# CACHE_NAME E LOCK_MODE LOCK_REQUEST TXN      SADDR    BLOCKING_SESSION
  --- -------------- ----- ----- -------- ------ ---------- - --------- ------------ -------- -------- ----------------
  550 TEST_TAB_BIND   6779  5950 9C79F958      8 dc_objects Y         0            5 AE92C090 B8D1D508              186
  186 TEST_TAB_BIND  28027 27359 9C79F958      8 dc_objects Y         5            0 AE92C090 B8990BB8
  550 TEST_TAB_BIND  28026 27359 9C79F958      8 dc_objects Y         0            5 AE92C090 B8D1D508              186
  186 TEST_TAB_BIND   6780  5950 9C79F958      8 dc_objects Y         5            0 AE92C090 B8990BB8
dia0 also shows blocking chain and call stack:

--testdb_dia0_10885_base_1.trc

*** 2021-06-25T16:19:35.806182+02:00
HM: Session with ID 550 serial # 22155 (FG) on single instance 1 is hung
    and is waiting on 'row cache lock' for 334 seconds.
    Final Blocker is Session ID 186 serial# 60254 on instance 1
     which is 'not in a wait' for 586 seconds
     
-------------------------------------------------------------------------------
Chain 1:
-------------------------------------------------------------------------------
    Oracle session identified by:
    {
              session id: 550
    }
    is waiting for 'row cache lock' with wait info:
    {
                      p1: 'cache id'=0x8
                      p2: 'mode'=0x0
                      p3: 'request'=0x5
            time in wait: 1.370457 sec (last interval)
            time in wait: 5 min 34 sec (total)  
    }
    and is blocked by
 => Oracle session identified by:
    {
              session id: 186
    }
              
HM: Short Stack of immediate waiter session ID 550 serial# 22155, OSPID 11575 (FG)
Short stack dump: 
ksedsts()+426<-ksdxfstk()+58<-ksdxcb()+872<-sspuser()+200<-__sighandler()<-semtimedop()+10<-skgpwwait()+192<-ksliwat()+2192<-kslwaitctx()+200
<-kqrget()+1397<-kqrLockPo()+1644<-kqrpre1()+2373<-kqlidp0_int()+5990<-kqlidp0()+521<-atbFMdrop()+692<-atbdrv()+7722<-opiexe()+30672
 
HM: current SQL: alter table TEST_TAB_2 drop partition P_1
After the test, quit debugger to complete both partition dropping.


4.2 Trace Files


We will add one partition into 'TEST_TAB_1', and then trace the activities of partition drop: (see Blog: Oracle row cache objects Event: 10222, Dtrace Scripts (I) )

Add one partition:

exec add_part('TEST_TAB_1', 1);
Then drop it with tracing:

alter session set max_dump_file_size = UNLIMITED;
alter session set events='10046 trace name context forever, level 12 : 
                          10704 trace name context forever, level 3 : 
                          10222 trace name context forever, level 4294967295' 
                  tracefile_identifier='rcl_1';

exec drop_part('TEST_TAB_1', 1);

alter session set events='10046 trace name context off : 10704 trace name context off : 10222 trace name context off'; 
In the first part of trace file, we can see that "name=TEST_TAB_1" (po 0x9c971fa8) is gotten in "mode=S". That is probably why P3=3 (share mode) in case of table/partition 'row cache lock' contention. The flag: "transaction=(nil)" also shows that there is no transaction in this part:

KQR: cid  8 bucket  19145 marked HOT
kqrpre: start hash=7c144ac9 mode=S keyIndex=0 dur=CALL opt=FALSE hot=TRUE
kqrScan: hot hash chain: processing po 0x9c971fa8
kqrpre: found cached object po=0x9c971fa8 flg=2
kqrmpin : kqrpre: found po Pin po 0x9c971fa8 cid=8 flg=2 hash=7c144ac9
time=2774049915
kqrpre: pinned po=0x9c971fa8 flg=2 pso=0xa0294020
pinned stack po=0x9c971fa8 cid=8: 
----- Abridged Call Stack Trace -----
ksedsts()+426<-kqrpre()+1985<-kkdlgonm()+229<-ktfa_check_ddl_fda_tables()+729<-opiSem()+23554<-opiprs()+321<-kksParseChildCursor()+527
<-rpiswu2()+2004<-kksLoadChild()+5283<-kxsGetRuntimeLock()+1983<-kksfbc()+18656<-kkspsc0()+1566<-kksParseCursor()+114<-opiosq0()+2310
----- End of Abridged Call Stack Trace -----
Partial short call stack signature: 0x4d19b7541a515b26
kqrAllocateEnqueue: bis: po=0x9c971fa8 flag=00010000 before=00000002 after=00010002
kqrget bic: po=0x9c971fa8 flag=00010000 before=00010002 after=00000002
kqrpre: done po=0x9c971fa8 cid=8 flg=2 eq=0x9cfbbb88 pso=0xa0294020 dur=CALL
kqrpre: keyIndex=0 hash=7c144ac9 99535881 0
kqrpre: obobn=4547616 obname=TEST_TAB_1 obtyp=2 obsta=1 obflg=0
kqrpre: SQL=alter table TEST_TAB_1 drop partition P_1 kqrpre: time=2774053161
kqrpre: po=0x9c971fa8 cid=8 stack: 
----- Abridged Call Stack Trace -----
ksedsts()+426<-kqrpreDoneTrace()+613<-kqrpre()+2954<-kkdlgonm()+229<-ktfa_check_ddl_fda_tables()+729<-opiSem()+23554<-opiprs()+321<-
----- End of Abridged Call Stack Trace -----
kqrpre: returnVal: TRUE
kqrprl: eq=0x9cfbbb88 fpInUse=FALSE

kqreqd: eq=0x9cfbbb88 res=0x9c972078
----------------------------------------
row cache enqueue: count=0 session=0xb8990bb8 object=0x9c971fa8, mode=S
flag=00 -/-/-/-/-/-/-/- savepoint=0x91da37
row cache parent object: addr=0x9c971fa8 cid=8(dc_objects)
hash=7c144ac9 typ=1 transaction=(nil) flags=00000002
objectno=4547616 ownerid=49 nsp=1
name=TEST_TAB_1
own=0x9c972078[0x9cfbbc08,0x9cfbbc08] wat=0x9c972088[0x9c972088,0x9c972088] mode=S req=N
status=VALID/-/-/-/-/-/-/-/-/-/-/-  KGH pinned  
            [........]        
kqreqd: processing po=0x9c971fa8 flag=2 mode=S
kqrmupin : kqrReleaseLock Unpin po 0x9c971fa8 cid=8 flg=2 hash=7c144ac9 time=2774055412
kqreqd freed enqeue: eq: 0x9cfbbb88
time=2774055435
            [........] 
In the second part of trace file, we can see all three involved objects (partition, program, table):

  name=TEST_TAB_1subname=P_1
  name=TEST_TAB_BIND
  name=TEST_TAB_1
which are all gotten in "mode=X".

For table "name=TEST_TAB_1", in the first part, it is gotten in "mode=S", in the second part, it is gotten in "mode=X".

For Plsql program "name=TEST_TAB_BIND", it is only gotten once in this second part, that is "mode=X" . That is probably why (P3=5 (exclusive)) in case of Plsql program 'row cache lock' contention. The flag: "transaction=0xae92d7e0" shows that they are all involved in one (recursive) transaction.

Here some extract lines from trace file of 'dc_objects' and transaction call stacks (xctCommitTxn).
           
kqreqd: eq=0x9a64a500 res=0x9c8f2d40
----------------------------------------
SO: 0x9caf9990, type: row cache enqueues (111), map: 0x9a64a500
SOC: 0x9a64a500, type: row cache enqueues (111), map: 0x9caf9990
row cache enqueue: count=2 session=0xb8990bb8 object=0x9c8f2c70, mode=X
flag=00 -/-/-/-/-/-/-/- savepoint=0x91dd75
row cache parent object: addr=0x9c8f2c70 cid=8(dc_objects)
hash=28cd6d03 typ=1 transaction=0xae92d7e0 flags=00002001
objectno=4605248 ownerid=49 nsp=1
name=TEST_TAB_1subname=P_1
own=0x9c8f2d40[0x9a64a580,0x9a64a580] wat=0x9c8f2d50[0x9c8f2d50,0x9c8f2d50] mode=X req=N
status=EMPTY/-/-/-/-/-/-/-/NEEDS INV/-/-/-  KGH pinned  
            [........]        
kqreqd: processing po=0x9c8f2c70 flag=2001 mode=X
kqrInvalidateObject clear: ob=0x9c8f2c70 before=00002001 after=00000000
kqrmupin : kqrReleaseLock Unpin po 0x9c8f2c70 cid=8 flg=0 hash=28cd6d03 time=2779225491
kqreqd freed enqeue: eq: 0x9a64a500
time=2779225499


kqreqd: eq=0x9d32f938 res=0x9c79fa28
----------------------------------------
SO: 0xa6e6c660, type: row cache enqueues (111), map: 0x9d32f938
SOC: 0x9d32f938, type: row cache enqueues (111), map: 0xa6e6c660
row cache enqueue: count=1 session=0xb8990bb8 object=0x9c79f958, mode=X
flag=40 -/-/-/-/-/-/XLG/- savepoint=0x91da6e
row cache parent object: addr=0x9c79f958 cid=8(dc_objects)
hash=2526973e typ=1 transaction=0xae92d7e0 flags=00000002
objectno=4509953 ownerid=49 nsp=1
name=TEST_TAB_BIND
own=0x9c79fa28[0x9d32f9b8,0x9d32f9b8] wat=0x9c79fa38[0x9c79fa38,0x9c79fa38] mode=X req=N
status=VALID/-/-/-/-/-/-/-/-/-/-/-  KGH pinned  
            [........]        
kqreqd: processing po=0x9c79f958 flag=2 mode=X
kqrmupin : kqrReleaseLock Unpin po 0x9c79f958 cid=8 flg=2 hash=2526973e time=2779225965
kqreqd freed enqeue: eq: 0x9d32f938
time=2779225973


kqreqd: eq=0x9cfb2428 res=0x9c972078
----------------------------------------
SO: 0xa6e79900, type: row cache enqueues (111), map: 0x9cfb2428
SOC: 0x9cfb2428, type: row cache enqueues (111), map: 0xa6e79900
row cache enqueue: count=6 session=0xb8990bb8 object=0x9c971fa8, mode=X
flag=00 -/-/-/-/-/-/-/- savepoint=0x91da49
row cache parent object: addr=0x9c971fa8 cid=8(dc_objects)
hash=7c144ac9 typ=1 transaction=0xae92d7e0 flags=00000002
objectno=4547616 ownerid=49 nsp=1
name=TEST_TAB_1
own=0x9c972078[0x9cfb24a8,0x9cfb24a8] wat=0x9c972088[0x9c972088,0x9c972088] mode=X req=N
status=VALID/-/-/-/-/-/-/-/-/-/-/-  KGH pinned  
            [........]        
kqreqd: processing po=0x9c971fa8 flag=2 mode=X
kqrmupin : kqrReleaseLock Unpin po 0x9c971fa8 cid=8 flg=2 hash=7c144ac9 time=2779226293
kqreqd freed enqeue: eq: 0x9cfb2428
time=2779226300


Abridged Call Stack Trace -----
ksedsts()+426<-kqrpre()+1985<-ktssexist_segment()+159<-ktssdro_segment()+317<-ktadrpc()+103
<-ktcccDeleteCommitCallbacks()+439<-ktcccdel()+46<-ktccpcmt()+310<-ktcCommitTxn_new()+1779
<-ktcCommitTxn()+94<-ktdcmt()+120<-k2lcom()+165<-k2send()+1220<-xctctl()+94
<-xctCommitTxn()+669<-opiexe()+19234
----- End of Abridged Call Stack Trace -----
In above trace, we can also see some lines like:

  kqrpre: optimistic lookup: hash=2546ac00
  kqrpre: optimistic lookup: searching cache 0 po 0xa22b6ac8 hash      226 ver 1
  kqrpre: optimistic lookup: searching cache 1 po 0xa22b6ac8 hash      226 ver 1
  ...
  kqrpre: optimistic lookup: searching cache 6 empty
  ...
  kqrpre: optimistic lookup: searching cache 9 po 0xa22b6ac8 hash      226 ver 1
  kqrpre: optimistic lookup: reading hash mtx addr=0xa85e7570 version=78
  kqrpre: optimistic lookup: processing po 0xa3def4a0 mtx addr=0xa3def640 version=82
  kqrpre: optimistic lookup: lock mode 0 flag 00000002
  kqrpre: optimistic lookup: fail
It can be toggled by a hidden parameter (18c changed default from FALSE to TRUE):

   _kqr_optimistic_reads	   optimistic reading of row cache objects	    TRUE


4.3. Row Cache Data Dump


With following command, we can dump 'dc_objects'.
(See MOS Bug 19354335 - Diagnostic enhancement for rowcache data dumps (Doc ID 19354335.8)
and Blog: Row Cache Object and Row Cache Mutex Case Study )
    
alter session set max_dump_file_size = UNLIMITED;
alter session set tracefile_identifier = 'dc_objects_dump';
-- dump level 0x82b: f is cache id 8 ('dc_objects'), 2 is single cacheiddump, b is level of 11
alter session set events 'immediate trace name row_cache level 0x82b';
alter session set events 'immediate trace name row_cache off';
The dump file consists of two parts, the first is ROW CACHE STATISTICS; and the second is 'dc_objects' data sorted by BUCKET:

ROW CACHE STATISTICS:
cache                          size     gets  misses  hit ratio  
--------------------------  -------  -------  ------  ---------  
dc_objects                     1056  3224531   49309      0.985  
  dc_object_grants               96     1673     308      0.845

dc_users                       2072  2877996     102      1.000  
  dc_users                      392        0       0      0.000
  dc_user_grants                376   353752      26      1.000
  dc_app_role                   320        0       0      0.000
  user's audit policies          64        0       0      0.000
  user's audit contexts         320        0       0      0.000
  CONTAINER_DATA_Attributes     584        0       0      0.000

WARNING: Restricting rowcache dump to just cacheid 8
ROW CACHE HASH TABLE: cid=8 ht=0xa8760de0 size=32768
Buckets with more than 20 objects:
NONE
Hash Chain Size     Number of Buckets
---------------     -----------------
              0                     0
              1                 11697
              2                  4810
              3                  1239
              4                   272
              5                    48
  ...
            >20                     0

BUCKET 5951:
  row cache parent object: addr=0x9c79f958 cid=8(dc_objects)
  hash=2526973e typ=1 transaction=(nil) flags=00000002
  objectno=4509953 ownerid=49 nsp=1
  name=TEST_TAB_BIND
  data=
  00000031 4554000d 545f5453 425f4241 00444e49 00000000 00000000 00000000 ...
  BUCKET 5951 total object count=1
             
BUCKET 19146:
  row cache parent object: addr=0x9c971fa8 cid=8(dc_objects)
  hash=7c144ac9 typ=1 transaction=(nil) flags=00000002
  objectno=4547616 ownerid=49 nsp=1
  name=TEST_TAB_1
  data=
  00000031 4554000a 545f5453 315f4241 00000000 00000000 00000000 00000000 ...
  BUCKET 19146 total object count=1
  
BUCKET 27908:
  row cache parent object: addr=0x9c8f2c70 cid=8(dc_objects)
  hash=28cd6d03 typ=1 transaction=(nil) flags=00000000
  objectno=4605248 ownerid=49 nsp=1
  name=TEST_TAB_1  subname=P_1
  data=
  00000031 4554000a 545f5453 315f4241 00000000 00000000 00000000 00000000 ...
  BUCKET 27908 total object count=1
In DB alert.log, we can see some buckets marked HOT:

2021-06-25T17:43:19.272935+02:00
KQR: cid 10 bucket  20941 marked HOT
KQR: cid  8 bucket  22482 marked HOT
KQR: cid  2 bucket  11264 marked HOT
which are probably checked by:

     kqrScan: reached _kqr_max_hot_copies limit %d
but "_kqr_max_hot_copies" is not (yet) exposed (maybe it will be similar to "_kgl_hot_object_copies").

Update 2023-Aug-23: "_kqr_max_hot_copies" is now exposed in Oracle 21c

   _kqr_max_hot_copies: upper limit of object hot copies allowed (default 16)
In DB alert.log, we can see:

2023-08-18T05:14:13.661259+02:00
  KQR row cache hit _kqr_max_hot_copies limit 16 cid 10 bucket 17930
2023-08-18T06:21:09.844089+02:00
  KQR row cache hit _kqr_max_hot_copies limit 16 cid 10 bucket 31900

2023-08-18T11:21:01.397443+02:00
  KQR row cache contention check: gets 1000000 sleeps 5577 limit 5000
  KQR row cache hash chain marked hot: cid 0 hash f27284a6
  KQR: cid  0 bucket      6 marked HOT (kqrpre: mutex sleeps exceed limit)
MOS "Bug 31135517 - High row cache mutex waits on hot objects - superseded (Doc ID 31135517.8)" describes that the fix implements 'hot object cloning' to alleviate row cache mutex contention.

In Trace Files, we can also see the 19.9 new introduced subroutine "kqrScan" (called by kqrpre or kqrpre2).

Sunday, June 13, 2021

Oracle dbms_alert and its Wait Events

dbms_alert provides asynchronous notification of database events with dbms_pipe (non-transactional) between receiver and sender, and make it transactional (ACID properties) by dbms_lock via table dbms_alert_info.

In this Blog, we will look three wait events in using dbms_alert:

  -. pipe get
  -. enq: UL - contention
  -. enq: TX - row lock contention
We will show that both receiver session (waitany/waitone) and sender session (signal) can be blocked session or blocking session.

Note: Tested in Oracle 19c


1. Receiver and Sender Lock Mode


dbms_alert receiver (waitany/waitone) requests SX_Mode, whereas Sender (signal) requests S_Mode.

Refer to Compatibility Rules defined in dbms_lock:

  Lock Compatibility Rules:
    held  get->  NL   SS   SX   S    SSX  X
    NL           SUCC SUCC SUCC SUCC SUCC SUCC
    SS           SUCC SUCC SUCC SUCC SUCC fail
    SX           SUCC SUCC SUCC fail fail fail
    S            SUCC SUCC fail SUCC fail fail
    SSX          SUCC SUCC fail fail fail fail
    X            SUCC fail fail fail fail fail
we can see that lock mode (SX) is compatible (SUCC) between two receivers, lock mode (S) is also compatible (SUCC) between two senders, but it is not compatible (fail) between receiver and sender. So receiver blocks sender, and sender blocks receiver.
(Look middle diagonal, except pair (SX, S)=fail, (S, SX)=fail and (S, S)=SUCC, all in diagonal and above it are "SUCC", all under it are "fail").

To test above Compatibility Rules, we can open two Sqlplus sessions, then run one of following blocks in one of them (there are 4 combinations).

-- Receiver (procedure waitany) session
declare
  register_name  varchar2(30) := 'TEST_ALERT_1';
  receiver_session_id   varchar2(30)   := dbms_session.unique_session_id;  -- sid||serial#||instance_number  (each 4 hex numbers)
  lockid         integer;
  lock_status    integer;
begin
   lockid      := dbms_utility.get_hash_value(register_name, 2000000000, 2048);
   lock_status := dbms_lock.request(lockid, dbms_lock.x_mode, dbms_lock.maxwait, release_on_commit => true);
   insert into dbms_alert_info values (register_name, receiver_session_id, 'N', null);
   commit;
end;
/

-- Sender (procedure signal) session
declare
  lockid       integer := dbms_utility.get_hash_value('TEST_ALERT_1', 2000002048, 2048);
  lock_status  integer;
begin
  lock_status := dbms_lock.request(lockid, dbms_lock.s_mode, dbms_lock.maxwait, release_on_commit => true);
  dbms_output.put_line('lockide='||lockid||', Status='||lock_status);
end;
/

  --SX:  name|mode=1431044099 = 0x554C0003 = UL-3
  --S:   name|mode=1431044100 = 0x554C0004 = UL-4
  --X:   name|mode=1431044102 = 0x554C0006 = UL-6 (used by register)


2. Wait Event: "pipe get" and "enq: UL - contention"


Open two Sqlplus sessions, R1 as receiver, S1 as sender. Then run following test steps.


2.1 R1@T1 - Receiver register an alert name


At first, we register an alert name:

exec dbms_alert.register('TEST_ALERT_1');
It looks like:

-- Register (procedure register)
declare
  register_name  varchar2(30) := 'TEST_ALERT_1';
  receiver_session_id   varchar2(30)   := dbms_session.unique_session_id;  -- sid||serial#||instance_number  (each 4 hex numbers)
  lockid         integer;
  lock_status    integer;
begin
   lockid      := dbms_utility.get_hash_value(register_name, 2000000000, 2048);
   lock_status := dbms_lock.request(lockid, dbms_lock.x_mode, dbms_lock.maxwait, release_on_commit => true);
   insert into dbms_alert_info values (register_name, receiver_session_id, 'N', null);
   commit;
end;
/
We can see the registered name, its UNIQUE_SID (sid: 916) and LOCKID:
       
select * from sys.dbms_alert_info;

  NAME         SID          CHANGED  MESSAGE
  ------------ ------------ -------  -------
  TEST_ALERT_1 0394D2BB0001 N        

-- sid||serial#||instance_number  (each 4 hex numbers)
select lpad(trim(to_char(sid, 'XXXX')), 4, '0')||lpad(trim(to_char(serial#, 'XXXX')), 4, '0')||lpad(trim(to_char(inst_id, 'XXXX')), 4, '0')
		      my_unique_sid 
      ,dbms_session.unique_session_id unique_sid 
from gv$session where sid = 916;

  MY_UNIQUE_SID  UNIQUE_SID
  -------------  ------------
  0394D2BB0001   0394D2BB0001

select dbms_utility.get_hash_value('TEST_ALERT_1', 2000000000, 2048) lockid from dual;

      LOCKID
  ----------
  2000000030                  
   
  -- Note: Lockids from 2000000000 to 2147483647 are reserved for products supplied by Oracle Corporation.  


2.2 R1@T2 - Receiver wait for registered name to occur


We start waitany with trace and PL/SQL hierarchical profiler:

alter session set events='10046 trace name context forever, level 12: 10704 trace name context forever, level 15' 
                  tracefile_identifier='trc_1';                 
exec dbms_hprof.start_profiling('PLSHPROF_DIR', 'hprpf_1');

declare
   l_message varchar2(1800);
   l_status  pls_integer;
   l_name    varchar2(30);
begin
   dbms_alert.waitany(name=>l_name, message=>l_message, status=>l_status);
   --dbms_alert.waitone(name=>'TEST_ALERT_1', message=>l_message, status=>l_status);
   dbms_output.put_line('Received Name='||l_name||', Message='||l_message||', Status='||to_char(l_status));
end;
/

exec dbms_hprof.stop_profiling;
alter session set events='10046 trace name context off: 10704 trace name context off';
If we "tail -f trc_1", we can see:

WAIT nam='pipe get' ela= 1999731 handle address=2401626056 buffer length=4096 timeout=86400000 obj#=3481822 tim=216712862038
WAIT nam='pipe get' ela= 2999855 handle address=2401626056 buffer length=4096 timeout=86400000 obj#=3481822 tim=216715862043
WAIT nam='pipe get' ela= 3999860 handle address=2401626056 buffer length=4096 timeout=86400000 obj#=3481822 tim=216719862055
WAIT nam='pipe get' ela= 4999839 handle address=2401626056 buffer length=4096 timeout=86400000 obj#=3481822 tim=216724862058
WAIT nam='pipe get' ela= 4999825 handle address=2401626056 buffer length=4096 timeout=86400000 obj#=3481822 tim=216729862028
...
WAIT nam='pipe get' ela= 4999871 handle address=2401626056 buffer length=4096 timeout=86400000 obj#=3481822 tim=216774862040
WAIT nam='pipe get' ela= 1150252 handle address=2401626056 buffer length=4096 timeout=86400000 obj#=3481822 tim=216776012435

  -- obj#=3481822 = SYS.DBMS_ALERT_INFO
  -- handle address=2401626056 = 0x8F25E7C8
It shows that 'pipe get' is polling in an interval of 2, 3, 5, 5 ...5 seconds ("ela" in microsecond), and immediate return when receiving a signal (last 'pipe get' line "ela= 1150252").

We can see that the name of 'pipe get' handle address=2401626056 (0x8F25E7C8) is a concatenation of 'ORA$ALERT$' and above UNIQUE_SID:

select c.addr, c.hash_value, c.name, c.namespace from v$db_object_cache c where addr like '%8F25E7C8';

  ADDR     HASH_VALUE NAME                   NAMESPACE
  -------- ---------- ---------------------- ---------
  8F25E7C8 3820773965 ORA$ALERT$0394D2BB0001 PIPE
"tail -f hprpf_1" shows DBMS_ALERT workflow and DBMS_PIPE.RECEIVE_MESSAGE wait of 65 seconds (65150257 microseconds).

P#C PLSQL."SYS"."DBMS_UTILITY"::11."GET_HASH_VALUE"#2300a0448782c31d #574
P#X 6
P#C PLSQL."SYS"."DBMS_LOCK"::11."REQUEST"#e72e757eb117d2ba #111
P#X 158
P#C SQL."SYS"."DBMS_ALERT"::11."__static_sql_exec_line275" #275."6q6u3ab0st2pj"
P#! SELECT CHANGED, MESSAGE FROM DBMS_ALERT_INFO WHERE
P#X 572
P#C PLSQL."SYS"."DBMS_LOCK"::11."RELEASE"#3048d2af80817a01 #199
P#X 45
P#C PLSQL."SYS"."DBMS_PIPE"::11."RECEIVE_MESSAGE"#9d831f6c5a526d3e #163
P#X 65150257


2.3 S1@T3 - Send a signal


Wait about 60 seconds, then we send a signal from session S1:

exec dbms_alert.signal('TEST_ALERT_1', 'alert_msg_1');
Now "tail -f trc_1" shows that R1 wait is changed from 'pipe get' to 'enq: UL - contention' (lock mode (SX): 0x554C0003 = UL-3) and blocked by S1:

*** 2021-06-10T15:55:59.084868+02:00
ksucti: init DID: 0001-0035-00000082 WAIT nam='enq: UL - contention' ela= 1000005 name|mode=1431044099 id=2000000030 0=0 obj#=3481822 tim=216777013038
ksucti: init DID: 0001-0035-00000082 WAIT nam='enq: UL - contention' ela= 1999250 name|mode=1431044099 id=2000000030 0=0 obj#=3481822 tim=216779013052
ksucti: init DID: 0001-0035-00000082 WAIT nam='enq: UL - contention' ela= 3999985 name|mode=1431044099 id=2000000030 0=0 obj#=3481822 tim=216783014039
ksucti: init DID: 0001-0035-00000082 WAIT nam='enq: UL - contention' ela= 8000198 name|mode=1431044099 id=2000000030 0=0 obj#=3481822 tim=216791015030
ksucti: init DID: 0001-0035-00000082 WAIT nam='enq: UL - contention' ela= 16000115 name|mode=1431044099 id=2000000030 0=0 obj#=3481822 tim=216807016041
ksucti: init DID: 0001-0035-00000082 WAIT nam='enq: UL - contention' ela= 32000110 name|mode=1431044099 id=2000000030 0=0 obj#=3481822 tim=216839017040
ksucti: init DID: 0001-0035-00000082 WAIT nam='enq: UL - contention' ela= 32000237 name|mode=1431044099 id=2000000030 0=0 obj#=3481822 tim=216871018073
ksucti: init DID: 0001-0035-00000082 WAIT nam='enq: UL - contention' ela= 32000841 name|mode=1431044099 id=2000000030 0=0 obj#=3481822 tim=216903020049
ksucti: init DID: 0001-0035-00000082 WAIT nam='enq: UL - contention' ela= 4693093 name|mode=1431044099 id=2000000030 0=0 obj#=3481822 tim=216907714067
*** 2021-06-10T15:58:10.786177+02:00

  --  name|mode=1431044099 = 0x554C0003 = UL-3
'enq: UL - contention' is waiting in an interval of 1, 2, 4, 8, 16, 32, 32 ...32 seconds, and immediate return when the waited lockid is released by S2 (last 'enq: UL - contention' line "ela= 4693093").

Oracle dbms_alert package document wrote:
     Waitany call: The polling loop begins at a one second interval and exponentially backs off to 30 second intervals.

"tail -f hprpf_1" shows the similar info:

P#C PLSQL."SYS"."DBMS_LOCK"::11."REQUEST"#e72e757eb117d2ba #111
P#X 1000256
P#C PLSQL."SYS"."DBMS_LOCK"::11."REQUEST"#e72e757eb117d2ba #111
P#X 1999479
P#C PLSQL."SYS"."DBMS_LOCK"::11."REQUEST"#e72e757eb117d2ba #111
P#X 4000224
P#C PLSQL."SYS"."DBMS_LOCK"::11."REQUEST"#e72e757eb117d2ba #111
P#X 8000401
P#C PLSQL."SYS"."DBMS_LOCK"::11."REQUEST"#e72e757eb117d2ba #111
P#X 16000326
P#C PLSQL."SYS"."DBMS_LOCK"::11."REQUEST"#e72e757eb117d2ba #111
P#X 32000317
P#C PLSQL."SYS"."DBMS_LOCK"::11."REQUEST"#e72e757eb117d2ba #111
P#X 32000459
P#C PLSQL."SYS"."DBMS_LOCK"::11."REQUEST"#e72e757eb117d2ba #111
P#X 32001077
P#C PLSQL."SYS"."DBMS_LOCK"::11."REQUEST"#e72e757eb117d2ba #111
P#X 4693322
By the way, during 'enq: UL - contention', if one session registers the same alert name:

exec dbms_alert.register('TEST_ALERT_1');
which requests dbms_lock.x_mode, it will be blocked by:

name|mode=1431044102 = 0x554C0006 = UL-6 (dbms_lock.x_mode)


2.4 S1@T4 - Sender Commit


Wait about 120 seconds, then we issue the commit in session S2:

commit;
dbms_alert docu said that most of the calls in dbms_alert package, except for 'signal', do commits.


2.5 R1@T5 - Receiver Return


R1 returns with the received message.

Received Name=TEST_ALERT_1, Message=alert_msg_1, Status=0


3. Wait Event: 'pipe get', 'enq: UL - contention' and 'enq: TX - row lock contention'


Now we run one more complicated test case.

Open three Sqlplus sessions, R1 (sid: 916) as receiver, S1 (sid: 371) and S2 (sid: 366) as sender. Then run following test steps.


3.1 R1@T1 - Receiver register an alert name


exec dbms_alert.register('TEST_ALERT_1');


3.2 R1@T2 - Receiver wait for registered name to occur


declare
   l_message varchar2(1800);
   l_status  pls_integer;
   l_name    varchar2(30);
begin
   dbms_alert.waitany(name=>l_name, message=>l_message, status=>l_status);
   --dbms_alert.waitone(name=>'TEST_ALERT_1', message=>l_message, status=>l_status);
   dbms_output.put_line('Received Name='||l_name||', Message='||l_message||', Status='||to_char(l_status));
end;
/


3.3 S1@T3 - S1 send to R1 'TEST_ALERT_1' with message 'alert_msg_1'


exec dbms_alert.signal('TEST_ALERT_1', 'alert_msg_1');


3.4 S2@T4 - S2 send to R1 'TEST_ALERT_1' with message 'alert_msg_2'


In S2, We start dbms_alert.signal with trace and PL/SQL hierarchical profiler:

alter session set events='10046 trace name context forever, level 12: 10704 trace name context forever, level 15' 
                  tracefile_identifier='trc_2';                 
exec dbms_hprof.start_profiling('PLSHPROF_DIR', 'hprpf_2');

exec dbms_alert.signal('TEST_ALERT_1', 'alert_msg_2');

exec dbms_hprof.stop_profiling;
alter session set events='10046 trace name context off: 10704 trace name context off';
"tail -f trc_2" shows S2 is in wait 'enq: UL - contention' (lock mode (S): 0x554C0004 = UL-4) blocked by Receiver R1 for about 23 seconds (ela= 23999086).

Then it is in 'enq: TX - row lock contention' for 64 seconds (ela= 64240041) blocked by Sender S1 till S1 commit.

BEGIN dbms_alert.signal('TEST_ALERT_1', 'alert_msg_2'); END;

*** 2021-06-10T17:02:29.147764+02:00
ksucti: WAIT: nam='enq: UL - contention' ela= 23999086 name|mode=1431044100 id=2000000030 0=0 obj#=58968 tim=220790075023

*** 2021-06-10T17:02:53.147188+02:00
=====================
UPDATE DBMS_ALERT_INFO SET CHANGED = 'Y', MESSAGE = :B2 WHERE NAME = UPPER(:B1 )


*** 2021-06-10T17:02:53.147277+02:00
ksucti: init session DID from txn DID: 0001-0032-000000DEBINDS #140582426666432:
*** 2021-06-10T17:02:53.147524+02:00
ksucti: WAIT: nam='enq: TX - row lock contention' ela= 64240041 name|mode=1415053318 usn<<16 | slot=524302 sequence=280482 obj#=3481822 tim=220854315708

*** 2021-06-10T17:03:57.387773+02:00

  -- name|mode=1431044100 = 0x554C0004 = UL-4
  -- name|mode=1415053318=54580006=TX-6
"tail -f hprpf_2" shows the same info:

P#C PLSQL."SYS"."DBMS_LOCK"::11."REQUEST"#e72e757eb117d2ba #111
P#X 23999402

P#C SQL."SYS"."DBMS_ALERT"::11."__static_sql_exec_line431" #431."6tmkh8j0d3w0p"
P#! UPDATE DBMS_ALERT_INFO SET CHANGED = 'Y', MESSAGE 
P#X 64241029


3.5 S1@T5 - Sender S1 Commit


Wait about 60 seconds, S1 commit:

commit;


3.6 S2@T6 - Sender S2 Commit


commit;


3.7 R1@T7 - Receiver Return


R1 returns with the received message 'alert_msg_2' ('alert_msg_1' was overwritten).

Received Name=TEST_ALERT_1, Message=alert_msg_2, Status=0
We can list session waiting history by query below:

select session_id, event, p1, p2, p3, blocking_session
      ,min(sample_time) start_time, max(sample_time) end_time
      ,max(sample_time) - min(sample_time) delta
from v$active_session_history v
where session_id in (916 ,371, 366)
  and sample_time between timestamp'2021-06-10 16:59:00' and timestamp'2021-06-10 17:05:00'
group by session_id, event, p1, p2, p3, blocking_session
order by min(sample_time);

  SESSION_ID EVENT                                  P1         P2     P3 BLOCKING_SESSION START_TIME END_TIME  DELTA
  ---------- ------------------------------ ---------- ---------- ------ ---------------- ---------- --------  --------
         916 enq: UL - contention           1431044099 2000000030      0              371 16:59:09   17:03:57  00:04:48
         366 enq: UL - contention           1431044100 2000000030      0              916 17:02:29   17:02:52  00:00:23
         366 enq: TX - row lock contention  1415053318     524302 280482              371 17:02:53   17:03:57  00:01:04
         916 enq: UL - contention           1431044099 2000000030      0              366 17:03:58   17:04:25  00:00:27
  

  Time Sequence:
    R1(sid 916)@T1=16:57:01 - dbms_alert.register('TEST_ALERT_1');
    R1(sid 916)@T2=16:58:02 - dbms_alert.waitany start
    S1(sid 371)@T3=16:59:09 - dbms_alert.signal('TEST_ALERT_1', 'alert_msg_1');
    S2(sid 366)@T4=17:02:29 - dbms_alert.signal('TEST_ALERT_1', 'alert_msg_2');
    S1(sid 371)@T5=17:03:57 - commit
    S2(sid 366)@T6=17:04:25 - commit
    R1(sid 916)@T7=17:04:25 - dbms_alert.waitany end
It shows that R1 (sid 916) is in 'enq: UL - contention' (P1=1431044100=UL-3(SX)) blocked by S1 (sid: 371) between 16:59:09 and 17:03:57, then blocked by S2 (sid: 366) between 17:03:58 and 17:04:25.

S2 (sid 366) is first in 'enq: UL - contention' blocked by Receiver R1 (sid: 916) for 23 seconds between 17:02:29 and 17:02:52, then in 'enq: TX - row lock contention' blocked by Sender S1 (sid: 371) for 64 seconds between 17:02:53 and 17:03:57 till S1 commit at 17:03:57.

In the first test, we showed that Receiver is in 'enq: UL - contention' for a timeout of 1, 2, 4, 8, 16, 32, 32 ...32 seconds.

In this test, S2 is first in 'enq: UL - contention' (P1=1431044100=UL-4(S)) for 23 seconds, that is because S2 sends signal after R1 is getting into an UL lock request with timeout of 32 seconds, 9 seconds already passed (See Note* below), and it still has remaining 23 seconds.

Note*: from 16:59:09 to 17:02:29 is 200 seconds, mod((200-(1+2+4+8+16)), 32) = 9 seconds.

Once the remaining 23 seconds passed, S2 gets requested lock (S1 has already UL-4(S) on lockid: 2000000030 and it is compatible with S2).

S2 is in 'enq: TX - row lock contention' because it executes:

   UPDATE DBMS_ALERT_INFO SET CHANGED = 'Y', MESSAGE = :B2 WHERE NAME = UPPER(:B1 )
after S1 has already executed it on the same row, and wait till its commit.

After timeout of 32 seconds at 17:02:52, R1 is back to make anew UL lock request (P1=1431044100=UL-3(SX)), it is first blocked by S1 till 17:03:57 (S1 commit), then blocked by S2 till 17:04:25 (S2 commit).


4. Waitone vs. Waitany


In the above test, if we use waitone, instead of waitany, in receiver session R1:

    --dbms_alert.waitany(name=>l_name, message=>l_message, status=>l_status);
    dbms_alert.waitone(name=>'TEST_ALERT_1', message=>l_message, status=>l_status);
the session wait events and blocking chains looks like:

select session_id, event, p1, p2, p3, blocking_session
      ,min(sample_time) start_time, max(sample_time) end_time
      ,max(sample_time) - min(sample_time) delta
from v$active_session_history v
where session_id in (916 ,371, 366) 
  and sample_time between timestamp'2021-06-10 17:06:00' and timestamp'2021-06-10 17:25:58'
group by session_id, event, p1, p2, p3, blocking_session
order by min(sample_time);

  SESSION_ID EVENT                        P1         P2  P3 BLOCKING_SESSION START_TIME END_TIME DELTA
  ---------- -------------------- ---------- ---------- --- ---------------- ---------- -------- --------
         916 enq: UL - contention 1431044099 2000000030   0              371 17:07:42   17:25:57 00:18:15
         366 enq: UL - contention 1431044100 2000000030   0              916 17:08:16   17:25:57 00:17:41
                                                                                              
select chain_signature, sid, blocker_sid, blocker_is_valid, in_wait_secs, time_remaining_secs, p1, p2, p3 from v$wait_chains v order by v.sid;

  CHAIN_SIGNATURE                                     SID BLOCKER_SID BLOCK IN_WAIT_SECS TIME_REMAINING_SECS         P1         P2  P3
  --------------------------------------------------- --- ----------- ----- ------------ ------------------- ---------- ---------- ---
  'SQL*Net message from client'<='enq: UL'<='enq: UL' 916         371 TRUE          1094                  -1 1431044099 2000000030   0
  'SQL*Net message from client'<='enq: UL'<='enq: UL' 371              FALSE         178                  -1 1413697536          1   0
  'SQL*Net message from client'<='enq: UL'<='enq: UL' 366         916 TRUE          1060                  -1 1431044100 2000000030   0

    -- P1=1431044099 = 0x554C0003 = UL-3
    -- P1=1431044100 = 0x554C0004 = UL-4
    * S1(sid 371) has a much smaller IN_WAIT_SECS (178) because we run monitoring queries in S1.
    
  Time Sequence:
    R1(sid 916)@T1=17:06:16 - dbms_alert.register('TEST_ALERT_1');
    R1(sid 916)@T2=17:06:46 - dbms_alert.waitone start
    S1(sid 371)@T3=17:07:42 - dbms_alert.signal('TEST_ALERT_1', 'alert_msg_1');
    S2(sid 366)@T4=17:08:16 - dbms_alert.signal('TEST_ALERT_1', 'alert_msg_2');
    
    query execution time@17:25:57
We can see that waitone is waiting 'enq: UL - contention' till signal committed (or timeout), not like waitany of polling loop of 1 to 32 second intervals.

If there is no alert registered, the behaviours of waitany and waitone are different, waitany returns ORU-10024 immediately, waitone will return Status=1 after the timeout period expires.

exec dbms_alert.removeall;
  PL/SQL procedure successfully completed.
  
select * from sys.dbms_alert_info;
  no rows selected

declare
   l_message varchar2(1800);
   l_status  pls_integer;
   l_name    varchar2(30);
begin
   dbms_alert.waitany(name=>l_name, message=>l_message, status=>l_status, timeout=>60);
   dbms_output.put_line('Received Name='||l_name||', Message='||l_message||', Status='||to_char(l_status));
end;
/

  ERROR at line 1:
  ORA-20000: ORU-10024: there are no alerts registered.
  ORA-06512: at "SYS.DBMS_ALERT", line 295
  ORA-06512: at line 6

declare
   l_message varchar2(1800);
   l_status  pls_integer;
   l_name    varchar2(30);
begin
   dbms_alert.waitone(name=>'TEST_ALERT_DUMMY', message=>l_message, status=>l_status, timeout=>60);
   dbms_output.put_line('Received Name='||l_name||', Message='||l_message||', Status='||to_char(l_status));
end;
/

  Received Name=, Message=, Status=1


5. Discussions


From above waiting history query result, we can see that blocked session can be Receiver or Sender (R1 or S2), and blocking session can also be Receiver or Sender (R1, S1 or S2).

For related discussions on dbms_alert, see
     Blog: dbms_alert lock hash collision investigated false 'enq: UL - contention' caused by lockid hash collision.
     Blog: One case of dbms_alert.signal deadlock demonstrated one case of deadlock generated by dbms_alert.signal.


6. dbms_alert Pseudo Code


Based on above tests, we can dipict dbms_alert logic in pseudo code as follows:

create table sys.dbms_alert_info
 (name     varchar2(30 byte),
  sid      varchar2(30 byte),
  changed  varchar2(1 byte),
  message  varchar2(1800 byte));

alter table sys.dbms_alert_info add (primary key (name, sid));
		
receiver_session_id   varchar2(30)   := dbms_session.unique_session_id;  -- sid||serial#||instance_number  (each 4 hex numbers)
receiver_pipename     varchar2(30)   := 'ORA$ALERT$' || receiver_session_id;
  
------------------------ Receiver ------------------------ 
register
   lockid      := dbms_utility.get_hash_value(register_name, 2000000000, 2048);
   lock_status := dbms_lock.request(lockid, dbms_lock.x_mode, dbms_lock.maxwait, release_on_commit => true);
   insert into dbms_alert_info values (register_name, receiver_session_id, 'N', null);

waitany
   --'pipe get' wait. Polling 2, 3, 4, 5, 5, ... 5 seconds. Immediate return when signal received (non-transactional).
   pipe_status := dbms_pipe.receive_message(receiver_pipename, timeout);  
   
   lockid  := dbms_utility.get_hash_value(register_name, 2000000000, 2048);
   waitime := 1;
   loop
     --'enq: UL - contention' wait. Iterate wait: 1, 2, 4, 8, ..32, ... 32 seconds. 
     -- Immediate return when received signal committed by sender (transactional).
     lock_status := dbms_lock.request(lockid, dbms_lock.sx_mode, waitime, release_on_commit => true);
   
     select changed, message from dbms_alert_info where sid = receiver_session_id and name = register_name;  -- 6q6u3ab0st2pj
     
     if changed = 'Y' then
       update dbms_alert_info set changed = 'N' where sid = :b2 and name = :b1;   -- 0g5b4j61q042t, reset Flag "changed"
       return message,
     end if;
          
     if lock_status = 1 then    -- timeout
       waitime := least(waitime*2, 32);  
     end if;
     
     if receiver_timeout then
       return 1;                -- timeout
     end if;
   end loop;   
    
------------------------ Sender ------------------------   
signal
   lockid := dbms_utility.get_hash_value(signal_name, 2000000000, 2048);
   lock_status := dbms_lock.request(lockid, dbms_lock.s_mode, dbms_lock.maxwait, release_on_commit => true);
   update dbms_alert_info set changed = 'Y', message = message where name = signal_name;  -- 6tmkh8j0d3w0p 
   pipe_status := dbms_pipe.send_message(receiver_pipename);

Wednesday, May 12, 2021

Oracle 19c new shared pool "SO private sga" and "SO private so latch" Performance Impacts

Oracle 19c introduced new shared pool component "SO private sga" and latch "SO private so latch". In this Blog, we will make tests to reveal their behaviours and performance impacts.

Normally Oracle can have up to 7 subpools in shared pool. However shared pool dump shows that "SO private sga" is only allocated to one single subpool. In such case, "SO private sga" can be one of the TOP 5 memory components, and hence high memory pressure on that particular subpool. Additionally "alter system flush shared_pool" cannot force the release of "SO private sga" memory.

The new 19c latch "SO private so latch" is positioned in LEVEL#=9, and "shared pool" latch LEVEL# is increased to 10 from 7 in 18c (latch ordering rule: Once a process acquires a latch of level x, it can only acquire another latch of level higher than x). To update "SO private sga" (kss_grow_from_global_cache), "SO private so latch" (kslgetl immediate_gets) is requested at first, which again requests library cache lock (kglLock) and mutex (kglGetMutex).

Note: Tested in Oracle 19.10

Update (08Jul2021): With test code of this Blog, Oracle delivered fix:
     Bug 32940955 : ORA-4031 DUE TO LARGE "SO PRIVATE SGA" ALLOCATION IN ONE SHARED POOL SUBPOOL


1. Test Setup


At first, we elaborate a standalone test, which can touch both "SO private sga" and "SO private so latch" for each new execution in a new Oracle connection.

create or replace package so_private_pkg as
  s_ts      timestamp with time zone;
  procedure proc1(p_cnt number);
end;
/

create or replace package body so_private_pkg as
  procedure proc1(p_cnt number) as
  begin
    s_ts  := systimestamp;   -- key to kss_grow_from_global_cache 
    dbms_session.set_nls('nls_territory',           'AMERICA');
    dbms_session.set_nls('nls_language',            'AMERICAN');
    dbms_session.set_nls('nls_sort',                'BINARY');
    dbms_session.set_nls('nls_numeric_characters',  '''.,''');
    commit;
    dbms_session.set_nls('nls_timestamp_format','''YYYY-MM-DD"T"HH24:MI:SS''');
  end;
end;
/

create or replace procedure K.start_job_so(p_count number) as
begin
  for i in 1..p_count loop
    dbms_scheduler.create_job (
      job_name        => 'TEST_JOB_SO_'||i,
      job_type        => 'PLSQL_BLOCK',
      job_action      =>
        'begin
           dbms_lock.sleep(29);
           so_private_pkg.proc1('||i||');
           --test_proc_dynamic(100, 1000);
           dbms_lock.sleep(1);
        end;',
      start_date      => systimestamp,
      repeat_interval => 'systimestamp',
      auto_drop       => true,
      enabled         => true);
  end loop;
end;
/


2. "SO private sga" and "SO private so latch"


Login to a 19c DB, we can see the new shared pool component "SO private sga":

select * from v$sgastat where name= 'SO private sga';

POOL           NAME                  BYTES   CON_ID
-------------- ---------------- ---------- --------
shared pool    SO private sga     40027648        0
If we make a shared pool dump, we can see "SO private sga" is only allocated to one single subpool (2) as TOP memory Subheap.

5 LARGEST SUB HEAPS for heap name="sga heap(2,0)"   desc=0x6015dc18
  Subheap ds=0x60006c98  heap name=  SO private sga  size=        40029040
   owner=(nil)  latch=(nil)
  Subheap ds=0x6000a060  heap name=  KSFD SGA I/O b  size=         4190424
   owner=(nil)  latch=(nil)
  Subheap ds=0x729ff880  heap name=   SQLA^15b219ae  size=         1242144
   owner=0x729ff730  latch=(nil)
  Subheap ds=0x9fd7ae80  heap name=   SQLA^9bd0be72  size=         1046128
   owner=0x9fd7ad30  latch=(nil)
  Subheap ds=0x8d9ebcf8  heap name=  PLMCD^e38f5ee0  size=         1038512
   owner=0x8dbbf2e0  latch=(nil)
Make a new connection and run test below to show latch usage:
(Note: For each new Connection, DO NOT RUN any script like glogin.sql (Site Profile file), login.sql (User Profile))

$ sqlplus /nolog

SQL> conn k/s@db19c
Connected.

SQL> select name, sum(gets), sum(immediate_gets) from v$latch_children where name in('shared pool', 'SO private so latch') group by name;

NAME                  SUM(GETS) SUM(IMMEDIATE_GETS)
-------------------- ---------- -------------------
shared pool            68985540               15463
SO private so latch     2793045             2808678

SQL> exec so_private_pkg.proc1(1);

PL/SQL procedure successfully completed.

SQL> select name, sum(gets), sum(immediate_gets) from v$latch_children where name in('shared pool', 'SO private so latch') group by name;

NAME                  SUM(GETS) SUM(IMMEDIATE_GETS)
-------------------- ---------- -------------------
shared pool            68985562               15463
SO private so latch     2793045             2808679
We can see "shared pool" SUM(GETS) increased 22 (68985562-68985540).
"SO private so latch" SUM(IMMEDIATE_GETS) increased 1 (2808679-2808678).

By the way, referring to x$ksmsp, we can see that Oracle shared pool memory is organized in 5 levels:

   shared_pool -> subpool (ksmchidx) -> component (ksmchcom) -> area (ksmchpar) -> chunk (ksmchptr)

select * from x$ksmsp where ksmchcom in ('SO private sga') and rownum <= 2;
 
  ADDR         INDX INST_ID CON_ID KSMCHIDX KSMCHDUR KSMCHCOM       KSMCHPTR KSMCHSIZ KSMCHCLS KSMCHTYP KSMCHPAR
  ------------ ---- ------- ------ -------- -------- -------------- -------- -------- -------- -------- --------
  7FBF49B7AF38    4       1      0        3        1 SO private sga 9FEFCBE8  1048536 recr         4095 9E29C270
  7FBF49B65BE8  837       1      0        3        1 SO private sga 9BFBDDA8   253976 freeabl         0 9E29C270
Following two queries list all memory components which are only allocated into one single subpool.

---- x$ksmsp lists each memomy chunk (ksmchptr, minimum unit) in each area (ksmchpar) for each component (ksmchcom) in subpool (ksmchidx)
---- x$ksmsp does not contain reserved extents
 
select ksmchcom, count(cnt) subpool_cnt, sum(siz) subpool_size
from (select ksmchidx, ksmchcom, count(*) cnt, sum(ksmchsiz) siz from x$ksmsp group by ksmchidx, ksmchcom)
  --where ksmchcom in ('SO private sga')
group by ksmchcom
having count(cnt) = 1
order by subpool_size desc; 
 
 
---- x$ksmss (v$sgastat) is about stats of SGA component (ksmssnam) in each subpool (ksmdsidx)
---- ksmdsidx = 0 is for reserved extents
 
select ksmssnam, count(cnt) subpool_cnt, sum(siz) subpool_siz 
  from (select ksmdsidx, ksmssnam, count(*) cnt, sum(ksmsslen) siz from x$ksmss group by ksmdsidx, ksmssnam)
  --where ksmssnam in ('SO private sga')
group by ksmssnam
having count(cnt) = 1
order by subpool_siz desc; 


3. SO private Activity Tracing


At frist, we get latch address, and then we compose one gdb script with those address (see Appendix "gdb_latch_script_3.txt")
(Note: the test DB is set with "_kghdsidx_count"=3 to create 3 subpools in shared pool)

select addr, latch#, child#, level#, name, gets, immediate_gets 
  from v$latch_children where name in ('shared pool', 'SO private so latch') and child# <=3 order by name, child#;

ADDR         LATCH#     CHILD#     LEVEL#  NAME                       GETS IMMEDIATE_GETS
-------- ---------- ---------- ----------  -------------------- ---------- --------------
B626A638         42          1          9  SO private so latch      931312         936136
B626A6F0         42          2          9  SO private so latch      930302         935888
B626A7A8         42          3          9  SO private so latch      931444         936680
60560A38        619          1         10  shared pool            22816547           5894
60560AD8        619          2         10  shared pool            22630853           5199
60560B78        619          3         10  shared pool            23540102           4370
Each time when making a test, we start a new connection:

SQL> conn k/s@db19c
Connected.
Get its UNIX process id: 123

Start tracing with the composed script:

gdb -x gdb_latch_script_3.txt -p 123
Run the test:

SQL> exec so_private_pkg.proc1(1);
Here the tracing log:

===== Library Cache Lock (1) <<< kgllkhdl: 879D79B0, kgllkmod 1, kglnaobj: BEGIN so_private_pkg.proc1(1); END;
===== Library Cache Lock (2) <<< kgllkhdl: 879DDD28, kgllkmod 1, kglnaobj: >>>=====
===== Library Cache Lock (3) <<< kgllkhdl: 6E851DD0, kgllkmod 1, kglnaobj: SO_PRIVATE_PKGK>>>=====
===== Library Cache Lock (4) <<< kgllkhdl: 707490A8, kgllkmod 1, kglnaobj: SO_PRIVATE_PKGK>>>=====
===== Library Cache Lock (5) <<< kgllkhdl: A2EA2A50, kgllkmod 1, kglnaobj: STANDARDSYS>>>=====
===== Library Cache Lock (6) <<< kgllkhdl: A5B6FA50, kgllkmod 1, kglnaobj: STANDARDSYS>>>=====
===== Library Cache Lock (7) <<< kgllkhdl: 87E9EC90, kgllkmod 1, kglnaobj: DBMS_SESSIONPUBLIC>>>=====

Breakpoint 9, 12d7f230 in kglGetMutex ()
=====--- kglGetMutex (30) ---> Mutex addr (rsi): A127C888, Location(r8d): 106
#0  12d7f230 in kglGetMutex ()
#1  12d74ea2 in kglhdgn ()

Breakpoint 10, 12da4830 in kgxExclusive ()
=====----- kgxExclusive (20) ---> Mutex addr (rsi): A127C888

Breakpoint 8, 12d76c90 in kgllkal ()
===== Library Cache Lock (8) <<< kgllkhdl: A127C738, kgllkmod 1, kglnaobj: DBMS_SESSIONSYS>>>=====
#0  12d76c90 in kgllkal ()
#1  12d72627 in kglLock ()

Breakpoint 1, 125c9ba0 in kslgetl ()
===== kslgetl shared latch (1) <<< Addr(rdi): 60560A38, Imget: 1, Why: 0, Where: 6293 >>>=====
#0  125c9ba0 in kslgetl ()
#1  1259da43 in ksfglt ()
#2  12d36f13 in kghalo ()
#3  12d33c24 in kghgex ()
#4  12d38442 in kghfnd ()
#5  12d36ae2 in kghalo ()
#6  12d7e6f8 in kglGetSO ()
#7  12d76dbf in kgllkal ()

Breakpoint 2, 125cf6c0 in kslfre ()
===== kslfre shared latch (1) <<< Addr(rdi): 60560A38 >>>=====
#0  125cf6c0 in kslfre ()
#1  1259dded in ksfflt ()

Breakpoint 11, 12da5850 in kgxRelease ()
=====----- kgxRelease (20) ---> Mutex addr (r15): A127C888 
$20 = {0, 909, 7105784, 253203}

......

===== Library Cache Lock (9) <<< kgllkhdl: 9EF37618, kgllkmod 1, kglnaobj: DBMS_SESSIONSYS>>>=====
===== Library Cache Lock (10) <<< kgllkhdl: 9EF30B90, kgllkmod 1, kglnaobj: alter session set nls_territory = AMERICA>>>=====
===== Library Cache Lock (11) <<< kgllkhdl: 9FAF9F50, kgllkmod 1, kglnaobj: alter session set nls_language = AMERICAN>>>=====
===== Library Cache Lock (12) <<< kgllkhdl: 9FAF8A28, kgllkmod 1, kglnaobj: alter session set nls_sort = BINARY>>>=====
===== Library Cache Lock (13) <<< kgllkhdl: A069FC50, kgllkmod 1, kglnaobj: alter session set nls_numeric_characters = '.,'>>>=====

......

Breakpoint 9, 12d7f230 in kglGetMutex ()
=====--- kglGetMutex (51) ---> Mutex addr (rsi): A1CA99D0, Location(r8d): 57
#0  12d7f230 in kglGetMutex ()
#1  04c9de7c in kglLockCursor ()

Breakpoint 10, 12da4830 in kgxExclusive ()
=====----- kgxExclusive (37) ---> Mutex addr (rsi): A1CA99D0

Breakpoint 8, 12d76c90 in kgllkal ()
===== Library Cache Lock (14) <<< kgllkhdl: A1CA9880, kgllkmod 1, kglnaobj: COMMIT>>>=====
#0  12d76c90 in kgllkal ()
#1  04c9df43 in kglLockCursor ()

Breakpoint 7, 012a0700 in kss_grow_from_global_cache ()
===== kss_grow_from_global_cache (1) <<  (r14): B7F27168, kss private so Chunk Addr (r14-4112): B7F26158 >>>=====
#0  012a0700 in kss_grow_from_global_cache ()
#1  1260c5ca in kss_add_child ()

Breakpoint 3, 125c9ba0 in kslgetl ()
===== kslgetl so private (1) <<< Addr(rdi): B626A6F0, Imget: 0, Why: 0, Where: 290 >>>=====
#0  125c9ba0 in kslgetl ()
#1  012a0844 in kss_grow_from_global_cache ()
#2  1260c5ca in kss_add_child ()
#3  12d7e4a5 in kglGetSO ()
#4  12d76dbf in kgllkal ()
#5  04c9df43 in kglLockCursor ()
#6  035b5e3f in kkspbd0 ()
#7  12a3bc9a in kksParseCursor ()

Breakpoint 4, 125cf6c0 in kslfre ()
===== kslfre so private (1) <<< Addr(rdi): B626A6F0 >>>=====
#0  125cf6c0 in kslfre ()
#1  012a088b in kss_grow_from_global_cache ()

Breakpoint 11, 12da5850 in kgxRelease ()
=====----- kgxRelease (37) ---> Mutex addr (r15): A1CA99D0 
$37 = {0, 909, 1253713, 34977169}

......

Breakpoint 8, 12d76c90 in kgllkal ()
===== Library Cache Lock (15) <<< kgllkhdl: 95589568, kgllkmod 1, kglnaobj: alter session set nls_timestamp_format = 'YYYY-MM-DD"T"HH24:MI:SS'>>>=====
#0  12d76c90 in kgllkal ()
#1  12d72627 in kglLock ()
The above output shows that latch get/free is fully contained within mutex get/release:

(1). kgxExclusive mutex get   A127C888
            kslgetl latch get   60560A38 (Imget: 1 for non IMMEDIATE_GETS)
            kslfre  latch free  60560A38
     kgxRelease mutex release A127C888 

(2). kgxExclusive mutex get   A1CA99D0
            kslgetl latch get   B626A6F0 (Imget: 0 for IMMEDIATE_GETS)
            kslfre  latch free  B626A6F0
     kgxRelease mutex release A1CA99D0 
The first latch get 60560A38 is triggered by kghalo to allocate shared pool memory.
Between kgxExclusive (mutex get) and kslgetl (latch get), there is one kgllkal (Library Cache Lock Allocate).
So the call sequence to allocate shared pool memory is kgxExclusive -> kgllkal -> kslgetl.

The second latch get B626A6F0 is triggered by kss_grow_from_global_cache to update (or insert) "SO private sga" memory component.

With following query, we can find the touched memory chunk for kss_grow_from_global_cache (r14): B7F27168 in "SO private sga".

select v.*, to_number('B7F27168', 'xxxxxxxx') - to_number(ksmchptr, 'xxxxxxxxxxxxxxxx') offset
  from x$ksmsp v 
 where ksmchcom in ('SO private sga') 
   and to_number('B7F27168', 'xxxxxxxx') between to_number(ksmchptr, 'xxxxxxxxxxxxxxxx') 
   and to_number(ksmchptr, 'xxxxxxxxxxxxxxxx') + ksmchsiz-1;
   
ADDR               INDX INST_ID  CON_ID KSMCHIDX   KSMCHDUR KSMCHCOM        KSMCHPTR         KSMCHSIZ KSMCHCLS KSMCHTYP KSMCHPAR         OFFSET
---------------- ------ ------- ------- -------- ---------- -------------- ----------------- -------- -------- -------- ---------------- -------
00007EFEA3952810 117341       1       0        2          1 SO private sga  00000000B7EF4878  1048536 recr     4095     0000000060006C98  207088
The trace log also contains Library Cache Locks/Pins (kglLock/kglpin), Mutex Gets/Releases (kgxExclusive and kgxRelease). For details, see Blog: Oracle PLITBLM "library cache: mutex X".


4. "latch: shared pool" and "library cache: mutex X" Blocking Test


Based on above tracing output, we can make two blocking wait tests.
One is with kslfre to block "latch: shared pool" get,
another is with kss_grow_from_global_cache to block "SO private sga" update.


4.1 kslfre Blocking Test


In this blocking test, we will show two wait events: "latch: shared pool", "library cache: mutex X", and then look their respective code path.

First we start 4 Job sessions:

exec start_job_so(4);
Then make a new DB connection.

SQL> conn k/s@db19c
Connected.
Get its UNIX process id: 9558 (Oracle session id: 909)

Start tracing and set a breakpoint

gdb -p 9558

break kslfre if $rdi==0x60560A38 || $rdi==0x60560AD8 || $rdi==0x60560B78
Run the test:

SQL> exec so_private_pkg.proc1(1);
Resume process running in gdb. After a few seconds, we reached the breakpoint, and display call stack.

(gdb) c
Continuing.

Breakpoint 1, 0x125cf6c0 in kslfre ()
(gdb) bt 16
#0  0x125cf6c0 in kslfre ()
#1  0x1259dded in ksfflt ()
#2  0x12d3605c in kghalo ()
#3  0x12d33c24 in kghgex ()
#4  0x12d38442 in kghfnd ()
#5  0x12d36ae2 in kghalo ()
#6  0x12d7e6f8 in kglGetSO ()
#7  0x12d76dbf in kgllkal ()
#8  0x12d72627 in kglLock ()
#9  0x12d6d4b5 in kglget ()
#10 0x04c86f19 in kglgob ()
#11 0x04c878bd in kglgob ()
#12 0x12d9b0c0 in kgiind ()
#13 0x056d98ec in pfri8_inst_spec ()
#14 0x056d9714 in pfri1_inst_spec ()
#15 0x12daed50 in pfrrun ()
We can see that all Job sessions are blocked with "latch: shared pool" or "library cache: mutex X" by session 909. So one session can cause two different wait events in the blocked sessions. (Note: we started 4 Job sessions, but v$session shows 6 due to dbms_scheduler job delayed start/stop cleanup).

v$mutex_sleep_history shows mutex sleeping stats by BLOCKING_SESSION 909.

v$latchholder shows that SID 909 is holding shared pool CHILD# 1 latch 60560A38.

SQL> select program, event, sid, serial#, p1, p2raw, p3raw, final_blocking_session
    from v$session
    where lower(program) like '%sql%' or lower(program) like '%j0%'
    order by program;

PROGRAM                        EVENT                      SID    SERIAL#  P1         P2RAW            P3RAW            FINAL_BLOCKING_SESSION
------------------------------ ------------------------- ------ --------- ---------- ---------------- ---------------- ----------------------
oracle@db19c (J000)         latch: shared pool            1011    58459   1616251448 000000000000026B 0000000093FD6BD0                    909
oracle@db19c (J001)         library cache: mutex X         426    51552   3168695887 0000038D00000000 0000130A0001006A                    909
oracle@db19c (J002)         library cache: mutex X         372     7360   3168695887 0000038D00000000 0000130A0001006A                    909
oracle@db19c (J003)         latch: shared pool             122    30477   1616251448 000000000000026B 000000008C6E1038                    909
oracle@db19c (J004)         library cache: mutex X         277     6387   3168695887 0000038D00000000 0000130A0001006A                    909
oracle@db19c (J005)         library cache: mutex X         408    13814   3168695887 0000038D00000000 0000130A0001006A                    909
sqlplus@db19c (TNS V1-V3)   SQL*Net message from client    909    33954   1413697536 0000000000000001 00
        
SQL> select mutex_identifier, sleep_timestamp, mutex_type, gets, sleeps, requesting_session, blocking_session, mutex_value, p1raw, location
     from v$mutex_sleep_history
     where sleep_timestamp > sysdate -2/1440 order by sleep_timestamp desc;

MUTEX_IDENTIFIER SLEEP_TIMESTAMP MUTEX_TYPE         GETS  SLEEPS REQUESTING_SESSION BLOCKING_SESSION MUTEX_VALUE      P1RAW            LOCATION
---------------- --------------- --------------- ------- ------- ------------------ ---------------- ---------------- ---------------- ------------
      3168695887 01:10:44        Library Cache   7108586   41476                277              909 0000038D00000000 00000000A127C738 kglhdgn2 106
      3168695887 01:10:44        Library Cache   7108586   41457                426              909 0000038D00000000 00000000A127C738 kglhdgn2 106
      3168695887 01:10:44        Library Cache   7108586   41470                408              909 0000038D00000000 00000000A127C738 kglhdgn2 106
      3168695887 01:10:44        Library Cache   7108586   41447                372              909 0000038D00000000 00000000A127C738 kglhdgn2 106
              15 01:10:38        Row Cache         46638       1                 76              360 0000016800000000 00               [19] kqrpre

  -- 38D in MUTEX_VALUE and P2RAW are blocking session id: 909 (=0x38D).

SQL> select * from v$latchholder;

 PID   SID LADDR     NAME                            GETS  CON_ID
---- ----- --------- --------------------------- -------- -------
  35   909 60560A38  shared pool                 22844512       0
  47  1011 6005F500  parameter table management  17143486       0
For "library cache: mutex X", query below finds the library object (P1RAW and P1 in above query output). ADDR is its object_handle (see Blog: Oracle PLITBLM "library cache: mutex X").

SQL > select hash_value, addr, owner, name, namespace, type from v$db_object_cache where hash_value in (3168695887);

HASH_VALUE ADDR             OWNER  NAME         NAMESPACE            TYPE
---------- ---------------- ------ ------------ -------------------- ----------
3168695887 00000000A127C738 SYS    DBMS_SESSION TABLE/PROCEDURE      PACKAGE
Now we can have a further look of two different wait events.


4.1.1 "library cache: mutex X" Wait


Above gdb trace shows that shared pool latch Get/Free is fully contained within mutex Get/Release. If the blocked session comes in the same code path, it is blocked by "library cache: mutex X" because it first invokes kglGetMutex (kgxExclusive) to get the same mutex.

Here is what diag LWS db19c_dia0_30593_lws_1.trc showed for Session ID 277. The Short stack dump shows kglGetMutex and kgxExclusive calls.

*** 2021-05-12T01:04:22.093264+02:00
HM: Early Warning - Session ID 277 serial# 6387 OS PID 5888 (J004)
     is waiting on 'library cache: mutex X' for 32 seconds, wait id 62
     p1: 'idn'=0xbcde764f, p2: 'value'=0x38d00000000, p3: 'where'=0x130a0001006a
    Final Blocker is Session ID 909 serial# 33954 on instance 1
     which is 'not in a wait' for 60 seconds
 
 Total  Self-         Total  Total  Outlr  Outlr  Outlr           
  Hung  Rslvd  Rslvd   Wait WaitTm   Wait WaitTm   Wait           
  Sess  Hangs  Hangs  Count   Secs  Count   Secs  Count Wait Event
------ ------ ------ ------ ------ ------ ------ ------ -----------
  2632      0      0 812304 7729347  12122 7695264      0 library cache: mutex X
 
HM: Dumping Short Stack of pid[61.5888] (sid:277, ser#:6387)
Short stack dump: 
ksedsts()+426<-ksdxfstk()+58<-ksdxcb()+872<-sspuser()+200<-__sighandler()<-semtimedop()+10<-skgpwwait()+187
<-ksliwat()+2224<-kslwaitctx()+188<-kgxWait()+1304<-kgxExclusive()+712<-kglGetMutex()+151<-kglhdgn()+898
<-kglLock()+562<-kglget()+293<-kglgob()+281<-kglgob()+2749<-kgiind()+4256<-pfri8_inst_spec()+140<-pfri1_inst_spec()+68
<-pfrrun()+544<-plsql_run()+752<-peicnt()+279<-kkxexe()+720<-opiexe()+25325<-kpoal8()+2387<-opiodr()+1202
<-kpoodr()+689<-upirtrc()+2760<-kpurcsc()+100<-kpuexec()+10994<-OCIStmtExecute()+41<-jslvec_execcb()+2537
<-jslvswu()+409<-jslvCDBSwitchUsr()+672<-jslve_execute0()+6761<-jslve_execute()+1529<-jslve_cdb_execute()+112
<-rpiswu2()+2004<-kkjex1e_cdb()+222<-kkjsexe()+2333<-kkjrdp()+1588<-opirip()+889<-opidrv()+581<-sou2o()+165
<-opimai_real()+173<-ssthrdmain()+417<-main()+256<-__libc_start_main()+245
Here oradebug short_stack for 'library cache: mutex X' wait:

SQL> oradebug setorapid 61  
Oracle pid: 61, Unix process pid: 5888, image: oracle@db19c (J004)

SQL> oradebug short_stack
ksedsts()+426<-ksdxfstk()+58<-ksdxcb()+872<-sspuser()+200<-__sighandler()<-semtimedop()+10<-skgpwwait()+187<-ksliwat()+2224
<-kslwaitctx()+188<-kgxWait()+1304<-kgxExclusive()+712<-kglGetMutex()+151<-kglhdgn()+898<-kglLock()+562<-kglget()+293<-kglgob()+281
<-kglgob()+2749<-kgiind()+4256<-pfri8_inst_spec()+140<-pfri1_inst_spec()+68<-pfrrun()+544<-plsql_run()+752<-peicnt()+279
<-kkxexe()+720<-opiexe()+25325<-kpoal8()+2387<-opiodr()+1202<-kpoodr()+689<-upirtrc()+2760<-kpurcsc()+100<-kpuexec()+10994
<-OCIStmtExecute()+41<-jslvec_execcb()+2537<-jslvswu()+409<-jslvCDBSwitchUsr()+672<-jslve_execute0()+6761<-jslve_execute()+1529
<-jslve_cdb_execute()+112<-rpiswu2()+2004<-kkjex1e_cdb()+222<-kkjsexe()+2333<-kkjrdp()+1588<-opirip()+889<-opidrv()+581
<-sou2o()+165<-opimai_real()+173<-ssthrdmain()+417<-main()+256<-__libc_start_main()+245


4.1.2 "latch: shared pool" Wait


There is also other code path, which directly requests 'latch: shared pool' and does not require mutex get. Here is what diag LWS db19c_dia0_30593_lws_1.trc showed for Session ID 122. The Short stack dump does not have kglGetMutex and kgxExclusive calls.

*** 2021-05-12T01:04:43.183727+02:00
HM: Early Warning - Session ID 122 serial# 30477 OS PID 5785 (J003)
     is waiting on 'latch: shared pool' for 52 seconds, wait id 61
     p1: 'address'=0x60560a38, p2: 'number'=0x26b, p3: 'why'=0x8c6e1038
    Final Blocker is Session ID 909 serial# 33954 on instance 1
     which is 'not in a wait' for 61 seconds 
                                                     IO           
 Total  Self-         Total  Total  Outlr  Outlr  Outlr           
  Hung  Rslvd  Rslvd   Wait WaitTm   Wait WaitTm   Wait           
  Sess  Hangs  Hangs  Count   Secs  Count   Secs  Count Wait Event
------ ------ ------ ------ ------ ------ ------ ------ -----------
    20      0      0  36534   9032     19   8544      0 latch: shared pool
 
HM: Dumping Short Stack of pid[60.5785] (sid:122, ser#:30477)
Short stack dump: 
ksedsts()+426<-ksdxfstk()+58<-ksdxcb()+872<-sspuser()+200<-__sighandler()<-semop()+7<-skgpwwait()+187<-kslges()+1534
<-kslgetl()+2489<-ksfglt()+163<-kghfre()+3985<-ksuxds()+1061<-kss_del_cb()+218<-kssdel()+216<-ksudel_int()+280
<-ksudel()+68<-kkjrdp()+2207<-opirip()+889<-opidrv()+581<-sou2o()+165<-opimai_real()+173<-ssthrdmain()+417
<-main()+256<-__libc_start_main()+245
Here oradebug short_stack for 'latch: shared pool' wait:

SQL> oradebug setorapid 60 
Oracle pid: 60, Unix process pid: 5785, image: oracle@db19c (J003)
SQL> oradebug short_stack
ksedsts()+426<-ksdxfstk()+58<-ksdxcb()+872<-sspuser()+200<-__sighandler()<-semop()+7<-skgpwwait()+187<-kslges()+1534<-kslgetl()+2489
<-ksfglt()+163<-kghfre()+3985<-ksp_param_handle_free()+779<-kspdesc()+142<-ksmugf()+208<-ksuxds()+3727<-kss_del_cb()+218
<-kssdel()+216<-ksudel_int()+280<-ksudel()+68<-kkjrdp()+2207<-opirip()+889<-opidrv()+581<-sou2o()+165<-opimai_real()+173
<-ssthrdmain()+417<-main()+256<-__libc_start_main()+245
In fact, if we use the same gdb script to trace one Job session, we can see the occurrence of kslgetl / kslfre on 60560A38 which is not contained within kglGetMutex kgxExclusive / kgxRelease.

Breakpoint 2, 0x125cf6c0 in kslfre ()
===== kslfre shared latch (14) <<< Addr(rdi): 60560B78 >>>=====
#0  0x125cf6c0 in kslfre ()
#1  0x1259dded in ksfflt ()

Breakpoint 1, 0x125c9ba0 in kslgetl ()
===== kslgetl shared latch (15) <<< Addr(rdi): 60560A38, Imget: 1, Why: 7165CBE8, Where: 6298 >>>=====
#0  0x125c9ba0 in kslgetl ()
#1  0x1259da43 in ksfglt ()
#2  0x12d2c341 in kghfre ()
#3  0x011380eb in ksp_param_handle_free ()
#4  0x01137d7e in kspdesc ()
#5  0x010f7990 in ksmugf ()
#6  0x1260e4cf in ksuxds ()
#7  0x1260503a in kss_del_cb ()

Breakpoint 2, 0x125cf6c0 in kslfre ()
===== kslfre shared latch (15) <<< Addr(rdi): 60560A38 >>>=====
#0  0x125cf6c0 in kslfre ()
#1  0x1259dded in ksfflt ()

Breakpoint 1, 0x125c9ba0 in kslgetl ()
===== kslgetl shared latch (16) <<< Addr(rdi): 60560AD8, Imget: 1, Why: 7D84F9C0, Where: 6298 >>>=====
db19c_dia0_30593_vfy_12.trc shows blocking graph for both wait events of two above sessions, which are blocked by session id: 909, and session id: 909 itself is in state 'CPU or Wait CPU' (although the fact is that session id: 909 is suspended by our manual breakpoint and in Process Status: t (stopped by debugger during trace) with %CPU=0.0, which does not consume any CPU).

*** 2021-05-12T01:05:52.275135+02:00
-------------------------------------------------------------------------------
Chain 1:
-------------------------------------------------------------------------------
    Oracle session identified by:
    {
                   os id: 5785
              process id: 60, oracle@db19c (J003)
              session id: 122
    }
    is waiting for 'latch: shared pool' with wait info:
    {
                      p1: 'address'=0x60560a38
                      p2: 'number'=0x26b
                      p3: 'why'=0x8c6e1038
            time in wait: 2 min 0 sec           
    }
    and is blocked by
 => Oracle session identified by:
    {
                   os id: 9558
              process id: 35, oracle@db19c
              session id: 909
             module name: 0 (SQL*Plusdb19c (TNS V1-V3))
    }
    which is on CPU or Wait CPU:
    {
               last wait: 2 min 30 sec ago
                blocking: 7 sessions
    }
 
Chain 1 Signature: 'CPU or Wait CPU'<='latch: shared pool'
===============================================================================
Chain 2:
-------------------------------------------------------------------------------
    Oracle session identified by:
    {
                   os id: 5888
              process id: 61, oracle@db19c (J004)
              session id: 277
    }
    is waiting for 'library cache: mutex X' with wait info:
    {
                      p1: 'idn'=0xbcde764f
                      p2: 'value'=0x38d00000000
                      p3: 'where'=0x130a0001006a
            time in wait: 2 min 1 sec
           timeout after: never
    }
    and is blocked by 'instance: 1, os id: 9558, session id: 909',
    which is a member of 'Chain 1'.


4.2 "SO private sga" kss_grow_from_global_cache Blocking Test


In this blocking test, only wait event: "library cache: mutex X" can be observed. We will also look its code path.

Make a new DB connection.

SQL> conn k/s@db19c
Connected.
Get its UNIX process id: 789 (Oracle session id: 909)

Start tracing and set a breakpoint

gdb -p 789

break kss_grow_from_global_cache
Run the test:

SQL> exec so_private_pkg.proc1(1);
Resume process running. After a few seconds, we reached the breakpoint, and display call stack.

(gdb) c
Continuing.

Breakpoint 1, 0x12a0700 in kss_grow_from_global_cache ()
(gdb) bt 21
#0  0x012a0700 in kss_grow_from_global_cache ()
#1  0x1260c5ca in kss_add_child ()
#2  0x12d7e4a5 in kglGetSO ()
#3  0x12d76dbf in kgllkal ()
#4  0x04c9df43 in kglLockCursor ()
#5  0x035b5e3f in kkspbd0 ()
#6  0x12a3bc9a in kksParseCursor ()
#7  0x12c11736 in opiosq0 ()
#8  0x129a7280 in opipls ()
#9  0x12990c52 in opiodr ()
#10 0x12aae556 in rpidrus ()
#11 0x12d501a1 in skgmstack ()
#12 0x12aae0d4 in rpidru ()
#13 0x12aad12f in rpiswu2 ()
#14 0x12aac4d2 in rpidrv ()
#15 0x12a89a83 in psddr0 ()
#16 0x12a88eb0 in psdnal ()
#17 0x12dbcfe2 in pevm_EXECC ()
#18 0x12db1a68 in pfrinstr_EXECC ()
#19 0x12db0544 in pfrrun_no_tool ()
#20 0x12daeeb6 in pfrrun ()
We can see that all Job sessions are blocked with "library cache: mutex X" by session 909. v$mutex_sleep_history shows mutex sleeping stats by BLOCKING_SESSION 909. (v$latchholder returns no rows because kslgetl is not yet invoked).

SQL> select program, event, sid, serial#, p1, p2raw, p3raw, final_blocking_session
    from v$session
    where lower(program) like '%sql%' or lower(program) like '%j0%'
    order by program;

PROGRAM                        EVENT                 SID   SERIAL#   P1        P2RAW            P3RAW            FINAL_BLOCKING_SESSION
------------------------------ -------------------- ------ --------- --------- ---------------- ---------------- ----------------------
oracle@db19c (J000)         library cache: mutex X    996     38685  255718823 0000038D00000000 0F3DF5A700000039                    909
oracle@db19c (J002)         library cache: mutex X    372     57666  255718823 0000038D00000000 0F3DF5A700000039                    909
oracle@db19c (J003)         library cache: mutex X    122     13135  255718823 0000038D00000000 0F3DF5A700000039                    909
oracle@db19c (J007)         library cache: mutex X   1011     38406  255718823 0000038D00000000 0F3DF5A700000039                    909
sqlplus@db19c (TNS V1-V3)   PGA memory operation      909     25675      65536 0000000000000001 00               

SQL> select mutex_identifier, sleep_timestamp, mutex_type, gets, sleeps, requesting_session, blocking_session, mutex_value, p1raw, location
     from v$mutex_sleep_history
     where sleep_timestamp > sysdate -2/1440 order by sleep_timestamp desc;

MUTEX_IDENTIFIER SLEEP_TIMESTAMP MUTEX_TYPE        GETS  SLEEPS REQUESTING_SESSION BLOCKING_SESSION MUTEX_VALUE      P1RAW            LOCATION
---------------- --------------- -------------  ------- ------- ------------------ ---------------- ---------------- ---------------- ---------------
       255718823 01:18:57        Library Cache  1254886    5155               1011              909 0000038D00000000 00000000A1CA9880 kgllkc1   57
       255718823 01:18:57        Library Cache  1254886    5145                122              909 0000038D00000000 00000000A1CA9880 kgllkc1   57
       255718823 01:18:57        Library Cache  1254886    5136                996              909 0000038D00000000 00000000A1CA9880 kgllkc1   57
       255718823 01:18:57        Library Cache  1254886    5141                372              909 0000038D00000000 00000000A1CA9880 kgllkc1   57
               0 01:17:36        Row Cache      4330026       1                122             1011 000003F300000000 00               [19] kqrpre

SQL> select * from v$latchholder;
  no rows selected


4.3 AWR Report


In AWR Section - Latch Sleep Breakdown, we can see stats of both "shared pool" and "SO private so latch".


Latch Sleep Breakdown

Latch Name                         Get Requests	 Misses  Sleeps Spin Gets
---------------------------------- ------------ ------- ------- ---------
cache buffers chains                 20,218,430   3,052     379     2,980
shared pool                             756,911   2,208     364     1,849
SO private so latch                      30,950      34       8	       27
kokc descriptor allocation latch            534       5       7         4
In Section - Latch Miss Sources, "SO private so latch" is displayed with correct Latch Name.

However, no Latch Name "shared pool" can be found. Probably it is re-named as "unknown". The location "Where" clearly shows that "kghalo" and "kghfre" (heap manager allocation/free). The sum of Sleeps for "unknown latch" is almost same as Sleeps (364) in above Section - Latch Sleep Breakdown.


Latch Miss Sources

Latch Name            Where                       NoWait Misses    Sleeps   Waiter Sleeps
--------------------- --------------------------- --------------  --------  -------------
SO private so latch   kss_grow_from_global_cache               0         7              0
SO private so latch   kss_shrink_private_so_list               0         1              8

unknown latch         kghalo                                   0       303            264
unknown latch         kghfre                                   0        31             78
unknown latch         kghupr1                                  0        15             12
unknown latch         kghalp                                   0         5              9
unknown latch         kgh_heap_sizes                           0         5              1
unknown latch         kghfnd: req scan                         0         2              0
Here the shared pool Child Latch Stats:

Child Latch Statistics

Latch Name    Child Num   Get Requests   Misses   Sleeps   Spin & Sleeps 1->3+
-----------  ----------  -------------  -------  -------  --------------------
shared pool           3        265,194      823      127      696/0/0/0
shared pool           2        256,577      775      149      629/0/0/0
shared pool           1        235,275      610       88      524/0/0/0
For other discussions of latch stats ("Get Requests", "Misses", "Sleeps", "Spin Gets"), see Blog: Is latch misses statistic gathered or deduced ?


4.4 "row cache mutex" Wait


diag LWS db19c_dia0_30593_lws_1.trc also shows "row cache mutex" Wait for 'cache id'=0xa (dc_users), which also involves 'latch: shared pool' ('address'=0x60560a38)

*** 2021-05-12T06:48:46.642290+02:00
HM: Early Warning - Session ID 277 serial# 6298 OS PID 7016 (J005)
     is waiting on 'row cache mutex' for 37 seconds, wait id 277
     p1: 'cache id'=0xa, p2: 'where requested'=0x13, p3: ''=0x0
    Blocked by Session ID 996 serial# 21988 on instance 1
     which is waiting on 'latch: shared pool' for 31 seconds
     p1: 'address'=0x60560a38, p2: 'number'=0x26b, p3: 'why'=0x0
    Final Blocker is Session ID 599 serial# 13104 on instance 1
     which is 'not in a wait' for 32 seconds
    Session ID 277 is blocking 2 sessions
    Blocking Session ID 765 serial# 16220 on instance 1
     which is waiting on 'row cache mutex' for 21 seconds
     p1: 'cache id'=0xa, p2: 'where requested'=0x13, p3: ''=0x0
                                                     IO           
 Total  Self-         Total  Total  Outlr  Outlr  Outlr           
  Hung  Rslvd  Rslvd   Wait WaitTm   Wait WaitTm   Wait           
  Sess  Hangs  Hangs  Count   Secs  Count   Secs  Count Wait Event
------ ------ ------ ------ ------ ------ ------ ------ -----------
    51      0      0 473776  30128    198  19008      0 row cache mutex
------------------------------------------

HM: Dumping Short Stack of pid[61.7016] (sid:277, ser#:6298)
Short stack dump: 
ksedsts()+426<-ksdxfstk()+58<-ksdxcb()+872<-sspuser()+200<-__sighandler()<-semtimedop()+10<-skgpwwait()+187<-ksliwat()+2224
<-kslwaitctx()+188<-kgxWait()+1304<-kgxExclusive()+712<-kqrGetPOMutexInt()+195<-kqrpre1()+792<-jsksGetDBObjectName()+771
<-jslvepost_exec_post()+1020<-jslvsst_session_stop()+5631<-jslve_execute0()+7742<-jslve_execute()+1529<-jslve_cdb_execute()+112
<-rpiswu2()+2004<-kkjex1e_cdb()+222<-kkjsexe()+2333<-kkjrdp()+1588<-opirip()+889<-opidrv()+581<-sou2o()+165<-opimai_real()+173
<-ssthrdmain()+417<-main()+256<-__libc_start_main()+245


5 Appendix gdb_latch_script_3.txt



set pagination off
set logging file latch_output_3.log
set logging overwrite on
set logging on
set $socg = 0
set $shcg = 0
set $socf = 0
set $shcf = 0
set $spec_lck = 0
set $spec_pin = 0
set $body_lck = 0
set $body_pin = 0
set $mutex_get = 0
set $mutex_ex = 0
set $mutex_fr = 0
set $mutex_addr = 0x0
set $body_locked = 0
set $sogrow = 0
set $i = 0

# Adjust beakpoint conditions
#   select name, listagg('$rdi==0X'||trim(leading 0 from addr), ' || ') within group (order by child#) addr
#    from v$latch_children where name in ('shared pool', 'SO private so latch') and child# <=3 group by name;
#   NAME                 ADDR
#   -------------------- ----------------------------------------------------
#   shared pool          $rdi==0X60560A38 || $rdi==0X60560AD8 || $rdi==0X60560B78
#   SO private so latch  $rdi==0xB626A638 || $rdi==0xB626A6F0 || $rdi==0xB626A7A8

# -- Usage: (1). make a trace new connection, (2). start gdb trace, (3). run a sql command, (4). stop gdb trace, (5). look trace output
# SQL> conn k/s@testdb
#      Connected.
# -- get spid (17191), gdb -x gdb_latch_script_3.txt -p 17191
# SQL> exec so_private_pkg.proc1(1);


break kslgetl if $rdi==0x60560A38 || $rdi==0x60560AD8 || $rdi==0x60560B78
commands
printf "===== kslgetl shared latch (%i) <<< Addr(rdi): %X, Imget: %i, Why: %X, Where: %i >>>=====\n", ++$shcg, $rdi, $rsi, $rdx, $rcx
backtrace 8
continue
end

break kslfre if $rdi==0x60560A38 || $rdi==0x60560AD8 || $rdi==0x60560B78
commands
printf "===== kslfre shared latch (%i) <<< Addr(rdi): %X >>>=====\n", ++$shcf, $rdi
backtrace 2
continue
end

break kslgetl if $rdi==0xB626A638 || $rdi==0xB626A6F0 || $rdi==0xB626A7A8
commands
printf "===== kslgetl so private (%i) <<< Addr(rdi): %X, Imget: %i, Why: %X, Where: %i >>>=====\n", ++$socg, $rdi, $rsi, $rdx, $rcx
backtrace 8
continue
end

break kslfre if $rdi==0xB626A638 || $rdi==0xB626A6F0 || $rdi==0xB626A7A8
commands
printf "===== kslfre so private (%i) <<< Addr(rdi): %X >>>=====\n", ++$socf, $rdi
backtrace 2
continue
end
    
break ksl_get_shared_latch if $rdi==0x60560A38 || $rdi==0x60560AD8 || $rdi==0x60560B78
commands
printf "===== ksl_get_shared_latch shared latch (%i) <<< addr(rdi): %X, Imget: %i, Why: %X, Where: %i, Mode: %X >>>=====\n", ++$i, $rdi, $rsi, $rdx, $rcx, $r8
backtrace 8
continue
end

break ksl_get_shared_latch if $rdi==0xB626A638 || $rdi==0xB626A6F0 || $rdi==0xB626A7A8
commands
printf "===== ksl_get_shared_latch SO latch (%i) <<< addr(rdi): %X, Imget: %i, Why: %X, Where: %i, Mode: %X >>>=====\n", ++$i, $rdi, $rsi, $rdx, $rcx, $r8
backtrace 8
continue
end

# Most "kss private so " has size 5136 in shared_poo dump like:   0b7f17b18 sz=     5136    cprm      "kss private so "
# Find Chunk which covers $r14. Offset 4112 to $r14 is only an example.
break kss_grow_from_global_cache
commands
printf "===== kss_grow_from_global_cache (%i) <<  (r14): %X, kss private so Chunk Addr (r14-4112): %X >>>=====\n", ++$sogrow, $r14, ($r14-4112)
backtrace 10
continue
end

break kgllkal 
#break kgllkal if $rdx==0X9FB08E08 || $rdx==0XA043B758
commands
printf "===== Library Cache Lock (%i) <<< kgllkhdl: %X, kgllkmod %x, kglnaobj: %s>>>=====\n", ++$body_lck, $rdx, $rcx, ($rdx+0x1c0)
backtrace 8 
continue
end

break kglGetMutex if $body_lck > 0 
command 
printf "=====--- kglGetMutex (%i) ---> Mutex addr (rsi): %X, Location(r8d): %d\n", ++$mutex_get, $rsi, $r8d
backtrace 4
continue
end

break kgxExclusive if $body_lck > 0 
#break kgxExclusive if $r9==0X9EC403D0 && $body_lck > 0 
command 
printf "=====----- kgxExclusive (%i) ---> Mutex addr (rsi): %X\n", ++$mutex_ex, $rsi
#backtrace 4
set $mutex_addr = $rsi  
continue
end

break kgxRelease if $r15==$mutex_addr && $body_lck > 0 
command 
printf "=====----- kgxRelease (%i) ---> Mutex addr (r15): %X \n", ++$mutex_fr, $r15 
p/d (int[4])*$r15
# x/4dw $r15     
continue
end