Tuesday, April 7, 2020

Index Service ITL and Recursive Transaction

Continuing with previous Blog:
     Cache Buffer Chains Latch Contention Case Study-1: Reverse Key Index
     Index Block Split Point Distribution,
this Blog will first demonstrates Index Service ITL usage and Recursive Transaction during index block splits, and then, as an example, gives a proof of their existence in index stats gathering.

We will trace transaction with Enqueue Trace Event 10704, and make index block / undo header block dumps to observe Index Service ITL and Recursive Transaction.


1. Test


First repeat our previous setup by inserting 4707 rows:

truncate table test_tab;
insert into test_tab select level, -level from dual connect by level <= 4707;
commit;
Then collect meta info:

select object_name, object_id, to_char(object_id, 'XXXXXXXX') id_hex, data_object_id, to_char(data_object_id, 'XXXXXXXX') did_hex 
from dba_objects where object_name in ('TEST_TAB', 'TEST_TAB#R');

  OBJECT_NAME  OBJECT_ID  ID_HEX  DATA_OBJECT_ID  DID_HEX
  -----------  ---------  ------  --------------  -------
  TEST_TAB     2459814    2588A6  2462199         2591F7
  TEST_TAB#R   2461548    258F6C  2462198         2591F6

-- index block for id: 1 to 11
select blk, count(*), min(id) min_id, max(id) max_id from (
  select id 
        ,dbms_rowid.rowid_block_number(sys_op_lbid (2461548, 'L', rowid)) blk
    from test_tab)  
where id between 1 and 11
group by blk order by 1, min_id, blk;

  BLK      COUNT(*)  MIN_ID  MAX_ID
  -------  --------  ------  ------
  3704663  11        1       11

-- find index root block for later dump
select segment_name, header_file, header_block, (header_block+1) root_block from dba_segments where segment_name = 'TEST_TAB#R';
  SEGMENT_NAME  HEADER_FILE  HEADER_BLOCK  ROOT_BLOCK
  ------------  -----------  ------------  ----------
  TEST_TAB#R    1548         3704658       3704659  
Trace row 4708 insert with Event 10704. First setup Trace Events:

alter session set events='10046 trace name context forever, level 1 : 10704 trace name context forever, level 3' 
                  tracefile_identifier='10704_r1_insert';
Then, open an UNIX window, start a Dtrace script to suspend process when Oracle gets to "ksqrcl:return" (session SPID: 543),

$ > sudo dtrace -w -n 'pid$target:oracle:ksqrcl:return {@CNT[ustack(5, 0)] = count(); stop(); exit(0);}' -p 543
Now make row 4708 insert:
           
insert into test_tab values(4708, -4708);
Immediately, process stopped, and Dtrace displays:
  
  oracle`ksqrcl+0xa
  oracle`ktucmt+0xe95
  oracle`ktcCommitTxn_new+0x35d
  oracle`ktccrb2+0x85
  oracle`kdisle+0x4a219
    1
From other Sqlplus session, run a query on gv$transaction:

select RECURSIVE, addr, xidusn, xidslot, xidsqn, status, ses_addr, start_scn, xid, prv_xid,
       flag, DECODE (BITAND (flag, 32), 0, 'NO', 'YES') flag_recur
  from gv$transaction v;

  RECURSIVE ADDR      XIDUSN XIDSLOT XIDSQN   STATUS   SES_ADDR  START_SCN     XID              PRV_XID           FLAG     FLAG_RECUR
  --------- --------- ------ ------- -------- -------- --------- ------------- ---------------- ----------------  -------- ----------
  NO        1866294E8 2      33      14402489 ACTIVE   18EE42590 9447170829632 02002100B9C3DB00 0000000000000000  3587      NO
  YES       18662A088 1      30      7334024  INACTIVE 18EE42590 9447170829684 01001E0088E86F00 02002100B9C3DB00  67116587  YES   
We can see two transactions for the same Oracle session (SES_ADDR: 18EE42590). One is marked RECURSIVE: NO; another is YES. Their ADDR and XID are different. Recursive transaction has its PRV_XID (02002100B9C3DB00) same as XID of main transaction. Recursive transaction is started after main transaction, which is showed by START_SCN: 9447170829632 < 9447170829684.

Index leaf block split also performed.

select blk, count(*), min(id) min_id, max(id) max_id from (
  select id 
        ,dbms_rowid.rowid_block_number(sys_op_lbid (2461548, 'L', rowid)) blk
    from test_tab)  
where id between 1 and 11
group by blk order by 1, min_id, blk;

  BLK      COUNT(*)  MIN_ID  MAX_ID
  -------  --------  ------  ------
  3704663  5         1       5
  3665613  6         6       11
Dump index root block (one block after header_block of segment TEST_TAB#R):

-- dump index root block 3704659 
alter session set tracefile_identifier = "index_root_block_3704659_insert";
alter system dump datafile 1548 block 3704659;

   Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
  0x01   0x0001.01e.006fe888  0x00c07b03.5855.03  --U-    1  fsc 0x0000.9742ed75  
Xid shows that it is done by RECURSIVE transaction, already committed (Flag: "--U-" for fast commit), Scn/Fsc filled with "0x0000.9742ed75".

So RECURSIVE transaction on root block is committed even that main transaction on leaf block is still open. Probably it is controlled by some Oracle internal mechanism, and more interestingly, this commit is independent to the commit of main transaction, so that contentions (locking duration) on root and branch blocks are minimized.

Note that there is only one Itl entry for index root and branch block, named Service Itl.

Open 10704 Trace file, all lines about TX are as follows:

  ksqgtl *** TX-00020021-00DBC3B9-00000000-00000000 mode=6 flags=0x401 timeout=0 ***
  ksqgtl: xcb=0x1866294e8, ktcdix=2147483647, topxcb=0x1866294e8
  
  ksqgtl *** TX-0001001E-006FE888-00000000-00000000 mode=6 flags=0x401 timeout=0 ***
  ksqgtl: xcb=0x18662a088, ktcdix=2147483647, topxcb=0x1866294e8
It shows two Transactions: TX-00020021 (Undo Segment 2, Slot 0x021=33) and TX-0001001E (Undo Segment 1, Slot 0x01E=30). TX-0001001E has TX-00020021 as topxcb=0x1866294e8 (TX-0001001E is a recursive transaction of TX-00020021, performed in the same session).

Now resume UNIX process (prun 543), 10704 Trace file is added with lines below. Recursive transaction: TX-0001001E returns with ksqrcl (release lock) although recursive transaction already committed before.

  ksqrcl: TX-0001001E-006FE888-00000000-00000000
  ksqrcl: returns 0
Terminate 10704 Trace:

alter session set events='10046 trace name context off : 10704 trace name context off ';  
Query v$transaction, only main transaction for row 4708 insert is open, recursive transaction committed:

-- XIDSQN: 14402489 = 0xdbc3b9
select xidusn, xidslot,xidsqn, ubafil, ubablk, ubasqn, ubarec, status from v$transaction;

  XIDUSN  XIDSLOT  XIDSQN    UBAFIL  UBABLK  UBASQN  UBAREC  STATUS
  ------ --------  --------  ------  ------  ------  ------  ------
  2       33       14402489  3       17248   18029   15      ACTIVE
Then make index block dump for the new split block, and look its two Itl (leaf block has at least two Itl, the first one is reserved as Service Itl):

alter system checkpoint;
alter system flush buffer_cache;

-- dump new index block 3665613 (contains row 6 and new row 4708)
alter session set tracefile_identifier = "index_block_3665613_insert";
alter system dump datafile 1548 block 3665613;

   Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
  0x01   0x0001.01e.006fe888  0x00c07b03.5855.01  CB--    0  scn 0x0897.9742ed75
  0x02   0x0002.021.00dbc3b9  0x00c04360.b993.0f  ----    1  fsc 0x0000.00000000
First Itl is committed, second Itl is still open (Flag: '----', Lck: 1, Scn/Fsc not filled). We can see first Itl is marked with Xid (0x0001.01e.006fe888), same as Itl of above index root block. So root block and leaf block split are performed by the same recursive transaction, but leaf block Uba (0x00c07b03.5855.01) is before root Uba (0x00c07b03.5855.03) (same "block address" and "block sequence number", but different "record within block", 01 vs. 03).

Find Undo Segment headers (segment_id 0x0001 and 0x0002) for both Itl:

-- Itl 0x01 is Service Itl 
select segment_name, file_id, segment_id, block_id from dba_rollback_segs where segment_id in (1, 2); 

  SEGMENT_NAME          FILE_ID  SEGMENT_ID  BLOCK_ID
  --------------------  -------  ----------  --------
  _SYSSMU1_1118279661$  3        1           776
  _SYSSMU2_3069567101$  3        2           872
Dump both Itl Undo Segment Headers:

-- dump Service Itl undo header 776 (Xid: 0x0001.01e.006fe888, undo segment 1, slot 0x01e)
alter session set tracefile_identifier = "undo_header_776_insert";
alter system dump datafile 3 block 776;

  index  state cflags  wrap#    uel         scn         dba        stmt_num    cmt
  ------ ----- ------ --------- ------- --------------- ---------- ----------- ---------- 
   0x1e    9    0x00  0x6fe888  0x0021  0x0897.9742ed75 0x00c07b03 0x00000000  1583849538   
   
-- dump current active (state 10) TRX  XID undo header 872 (Xid: 0x0002.021.00dbc3b9, undo segment 2, slot 0x021)
alter session set tracefile_identifier = "undo_header_872_insert";
alter system dump datafile 3 block 872;

  index  state cflags  wrap#    uel         scn         dba        stmt_num    cmt
  ------ ----- ------ --------- ------- --------------- ---------- ----------- ---
   0x21   10    0x80  0xdbc3b9  0x000b  0x0897.9742ed40 0x00c04360 0x00000000  0
First Itl is committed (state 9) and stamped with cmt (commit time): 1583849538. Second Itl is active (state 10).

Now trace commit with Event 10704:

alter session set events='10046 trace name context forever, level 1 : 10704 trace name context forever, level 3' 
                  tracefile_identifier='10704_r1_commit';
commit;
alter session set events='10046 trace name context off : 10704 trace name context off ';

  XCTEND rlbk=0, rd_only=0, tim=7414747162919
  ksqrcl: TX-00020021-00DBC3B9-00000000-00000000
  ksqrcl: returns 0
The main transaction TX-00020021 terminated (XCTEND) and released lock (ksqrcl).

Dump again index block of new split block:

alter system checkpoint;
alter system flush buffer_cache;

-- dump new index block 3665613 (contains row 6 and new row 4708)
alter session set tracefile_identifier = "index_block_3665613_commit";
alter system dump datafile 1548 block 3665613;

   Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
  0x01   0x0001.01e.006fe888  0x00c07b03.5855.01  CB--    0  scn 0x0897.9742ed75
  0x02   0x0002.021.00dbc3b9  0x00c04360.b993.0f  C---    0  scn 0x0897.9742efa1
Both Itl are committed. Scn/Fsc in second Itl (9742efa1) is bigger than that of first Itl (9742ed75).

Dump again both Undo Segment Headers:

-- dump Service Itl undo header 776 (Xid: 0x0001.01e.006fe888, undo segment 1, slot 0x01e)
alter session set tracefile_identifier = "undo_header_776_commit";
alter system dump datafile 3 block 776;

  index  state cflags  wrap#    uel         scn         dba        stmt_num    cmt
  ------ ----- ------ --------- ------- --------------- ---------- ----------- ---------- 
   0x1e    9    0x00  0x6fe888  0x0021  0x0897.9742ed75 0x00c07b03 0x00000000  1583849538 
   
-- dump just committed TRX XID undo header 872 (Xid: 0x0002.021.00dbc3b9, undo segment 2, slot 0x021)
alter session set tracefile_identifier = "undo_header_872_commit";
alter system dump datafile 3 block 872;

  index  state cflags  wrap#    uel         scn         dba        stmt_num    cmt
  ------ ----- ------ --------- ------- --------------- ---------- ----------- ----------
   0x21    9    0x00  0xdbc3b9  0x000b  0x0897.9742efa1 0x00c04360 0x00000000  1583850562  
Second Itl is also committed (state 9) and stamped with cmt: 1583850562, which is bigger than that of first Itl: 1583849538. So recursive transaction is committed before main transaction, and started after main transaction, or its lifecycle is totally inside main transaction.


2. Service ITL and Recursive Transaction with Index Stats


Now we can have a look of the impact caused by Recursive Transaction.

Open two Sqlplus sessions: SID1 and SID2.

In SID1, we create table and index with one row insert:

SID1 > 
  drop table test_service_itl purge;
  
  create table test_service_itl
    as select level x, rpad('Test Service ITL', 100, 'X') y from dual connect by level <= 1; 
    
  create index test_service_itl#i#1 on test_service_itl(x, y);
In SID2, insert 1,000,000 rows without commit:

SID2 > 
  insert into test_service_itl
    select level x, rpad('Test Service ITL', 100, 'X') y from dual connect by level <= 1e6; 
Back to In SID1, gather stats, run queries, and make index root block dump and index treedump.:

SID1 >
  exec dbms_stats.gather_table_stats(null, 'TEST_SERVICE_ITL', cascade=> true);

  select blevel, leaf_blocks, num_rows from dba_indexes v where index_name = 'TEST_SERVICE_ITL#I#1';
  
    BLEVEL  LEAF_BLOCKS  NUM_ROWS
    ------  -----------  --------
         2            1         1
  
  -- find index root block for later dump
  select segment_name, header_file, header_block, (header_block+1) root_block 
    from dba_segments where segment_name = 'TEST_SERVICE_ITL#I#1';
    
    SEGMENT_NAME                   HEADER_FILE HEADER_BLOCK ROOT_BLOCK
    ------------------------------ ----------- ------------ ----------
    TEST_SERVICE_ITL#I#1                  1678      2257586    2257587
  
  -- dump index root block 2257587 
  alter session set tracefile_identifier = "index_root_block_2257587";
  alter system dump datafile 1678 block 2257587;
  
     Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
    0x01   0x006c.00d.00003b62  0x00c1be06.0e31.04  C---    0  scn  0x000008ceef036f76
    Branch block dump
    =================
    header address 4233650252=0xfc58604c
    kdxcolev 2
    KDXCOLEV Flags = - - -
    kdxcolok 0
    kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y
    kdxconco 3
    kdxcosdc 2
    kdxconro 21
    kdxcofbo 70=0x46
    kdxcofeo 7846=0x1ea6
    kdxcoavs 7776
    kdxbrlmc 1795863=0x1b6717
    kdxbrsno 20
    kdxbrbksz 8056 
    kdxbr2urrc 0
    
  select object_name, object_id, to_char(object_id, 'XXXXXXXX') id_hex, 
                      data_object_id, to_char(data_object_id, 'XXXXXXXX') did_hex 
  from dba_objects where object_name = 'TEST_SERVICE_ITL#I#1';
  
             OBJECT_NAME  OBJECT_ID  ID_HEX  DATA_OBJECT_ID  DID_HEX
    --------------------  ---------  ------  --------------  -------
    TEST_SERVICE_ITL#I#1 3305086  326E7E         3305086   326E7E
  
  -- dump index treedump  
  alter session set tracefile_identifier = "index_treedump_3305086";
  alter session set events 'immediate trace name treedump level 3305086';

    ----- begin tree dump
    branch: 0x2272b3 2257587 (0: nrow: 22, level: 2)
       branch: 0x1b6717 1795863 (-1: nrow: 682, level: 1)
          leaf: 0x2272b5 2257589 (-1: row:69.69 avs:61)
          leaf: 0x2272b6 2257590 (0: row:69.69 avs:24)
          leaf: 0x2272b7 2257591 (1: row:68.68 avs:109)
          leaf: 0x18032d 1573677 (2: row:68.68 avs:108)
          leaf: 0x18032e 1573678 (3: row:68.68 avs:109)
The index stats shows that both NUM_ROWS and LEAF_BLOCKS are 1 since un-committed 1,000,000 rows are not gathered. However BLEVEL is 2 because BLEVEL is updated by Recursive Transaction with Index Service ITL, which has already committed as showed by the index root Itl:

     Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
    0x01   0x006c.00d.00003b62  0x00c1be06.0e31.04  C---    0  scn  0x000008ceef036f76
Therefore index meta data BLEVEL is computed independent of user data NUM_ROWS and LEAF_BLOCKS. BLEVEL is updated by recursive transaction, others are updated by main transaction.

The above test is an indirect proof of Service ITL and Recursive Transaction existence.

Index Block Split Point Distribution

Continuing with previous Blog: Cache Buffer Chains Latch Contention Case Study-1: Reverse Key Index, this Blog will look the split point distribution of index block, specially for Reverse Key Index.


1. Index Split Stats


In this case study, I have also learned from others to look index split stats in v$sesstat or v$sysstat.

For example, insert 1'000'000 rows, and look leaf/branch/root node splits.

truncate table test_tab;

select sid, n.name, s.value 
  from v$mystat s, v$statname n 
 where s.statistic# = n.statistic#
   and name in ('root node splits', 'branch node splits', 'leaf node splits');
   
insert into test_tab select level, -level from dual connect by level <= 1e6;
commit;

select sid, n.name, s.value 
  from v$mystat s, v$statname n 
 where s.statistic# = n.statistic#
   and name in ('root node splits', 'branch node splits', 'leaf node splits');   
Here the output:
   
  SID  NAME                VALUE
  368  leaf node splits    0
  368  branch node splits  0
  368  root node splits    0

SQL (368,56362) > insert into test_tab select level, -level from dual connect by level <= 1e6;
  1'000'000 rows inserted.
   
  SID  NAME                VALUE
  368  leaf node splits    2964
  368  branch node splits  4
  368  root node splits    1


2. Split Point Distribution


The appended Plsql: indx_splits_stats_get can be used to get the exact point of index split. For example, insert 10'000 rows, and look split point distribution:

exec indx_splits_stats_get(1e4, 1);

select t.*, ins_row -lag(ins_row) over(order by ins_row) rows_between
from  indx_splits_stats t
where run=1 
order by ins_row;   

  RUN  INS_ROW  SID  ROOT_SPLITS  BRANCH_SPLITS  LEAF_SPLITS  ROWS_BETWEEN
  ---  -------  ---  -----------  -------------  -----------  ------------
  1    541      902  0            0              1
  1    996      902  0            0              2            455
  1    1131     902  0            0              3            135
  1    1967     902  0            0              4            836
  1    1994     902  0            0              5            27
  1    2310     902  0            0              6            316
  1    2325     902  0            0              7            15
  1    3959     902  0            0              8            1634
  1    3986     902  0            0              9            27
  1    4047     902  0            0              10           61
  1    4074     902  0            0              11           27
  1    4442     902  0            0              12           368
  1    4625     902  0            0              13           183
  1    4708     902  0            0              14           83
  1    4719     902  0            0              15           11
  1    7563     902  0            0              16           2844
  1    7671     902  0            0              17           108
  1    7689     902  0            0              18           18
  1    7758     902  0            0              19           69
  1    7797     902  0            0              20           39
  1    7884     902  0            0              21           87
  1    8251     902  0            0              22           367
  1    8278     902  0            0              23           27
  1    8837     902  0            0              24           559
  1    8942     902  0            0              25           105
  1    9128     902  0            0              26           186
  1    9209     902  0            0              27           81
  1    9220     902  0            0              28           11
  1    9827     902  0            0              29           607
  
  29 rows selected.
There are 29 'leaf node splits', in average, 344 rows per split. However, column ROWS_BETWEEN shows that the minimum distance between two splits are 11 rows, the maximum is 2844, about 258 (2844/11) times of difference, an irregular and unpredictable split distribution.

If we rebuild index test_tab#r as noreverse, inserting 10'000 rows will have only 18 'leaf node splits', every split occurs exactly after 533 rows insert, an totally even distribution.


3. Test Code: indx_splits_stats_get



drop view sesstat_v;

create view sesstat_v as
select sid, n.name, s.value from v$sesstat s, v$statname n where s.statistic# = n.statistic#;

drop table indx_splits_stats;

create table indx_splits_stats as
select 0 run, 0 ins_row, sid
       ,sum(decode(name, 'root node splits', value, 0))   as root_splits
       ,sum(decode(name, 'branch node splits', value, 0)) as branch_splits
       ,sum(decode(name, 'leaf node splits', value, 0))   as leaf_splits
  from sesstat_v where sid = -1 group by sid;

create or replace procedure indx_splits_stats_get(p_ins_rows number, p_run number) as
  l_sid             number := sys.dbms_support.mysid;
  l_root_splits_pre     number := 0;
  l_branch_splits_pre   number := 0;
  l_leaf_splits_pre     number := 0;
  l_root_splits     number := 0;
  l_branch_splits   number := 0;
  l_leaf_splits     number := 0;  
begin
  execute immediate 'truncate table test_tab';

  select sum(decode(name, 'root node splits', value, 0))   as root_splits
        ,sum(decode(name, 'branch node splits', value, 0)) as branch_splits
        ,sum(decode(name, 'leaf node splits', value, 0))   as leaf_splits
        into l_root_splits_pre, l_branch_splits_pre, l_leaf_splits_pre
  from sesstat_v where sid = l_sid;
  
  for i in 1..p_ins_rows loop
    insert into test_tab values(i, -i);
    commit;
    select sum(decode(name, 'root node splits', value, 0))   as root_splits
          ,sum(decode(name, 'branch node splits', value, 0)) as branch_splits
          ,sum(decode(name, 'leaf node splits', value, 0))   as leaf_splits
          into l_root_splits, l_branch_splits, l_leaf_splits
    from sesstat_v where sid = l_sid;
   
   if l_root_splits != l_root_splits_pre or l_branch_splits != l_branch_splits_pre or l_leaf_splits != l_leaf_splits_pre 
   then
     dbms_output.put_line('--- Leaf Splitted at row count: '||i||
                          ', '||l_root_splits||','||l_branch_splits||','||l_leaf_splits||',');
     insert into indx_splits_stats values(p_run, i, l_sid, l_root_splits, l_branch_splits, l_leaf_splits);
     l_root_splits_pre   := l_root_splits;
     l_branch_splits_pre := l_branch_splits;
     l_leaf_splits_pre   := l_leaf_splits; 
     commit;
  end if; 
  end loop; 
end;
/

-- exec indx_splits_stats_get(1e4, 1);  

Cache Buffer Chains Latch Contention Case Study-3: index root block Modification Blocking

Continuing from previous Blog: Cache Buffer Chains Latch Contention Case Study-2: Reverse Primary Key Index, we will look index root (branch) block CBC latch blocking, and its impacts to entire system.

At first we will find one row in an unrelated noise table, which is hashed to the same CBC latch as index root block.

In the first session, we update that unrelated row, and stop it by suspending CBC latch request.

In the second session, insert one row into our test table to trigger a leaf block split, which results in root block modification (insert of new split leaf block). Since they share the same CBC latch, the second session is also blocked.

Consequently any other session (e.q. SMON, DB Writer) which requires that CBC latch is also blocked.

Note: Tested in Oracle 12c, 19c


1. Test Setup


Same as previous blog, we create a table with one unique key reverse index (usually recommended to reduce index contention):

drop table test_tab purge;

create table test_tab (id number, val number);

create unique index test_tab#r on test_tab(id) reverse;

alter table test_tab add constraint test_tab#r primary key (id);
and again find the same leaf block split point:

SQL > exec leaf_split_finder(5000);

  --- Leaf Splitted at row count: 4906, (id between 1 and 11) in 2 Blocks
  block: 2661263, cnt: 5, min_id: 1, max_id: 5
  block: 3665621, cnt: 6, min_id: 6, max_id: 11
Then create a noise table with one idnex: (see right linked book: Oracle Database Performance Tuning (Studies . Practices . Researches) - Chapter 3: Locks, Latches and Mutexes)

drop table cbc_noise purge;

create table cbc_noise 
  as select level x, rpad('CBC_NOISE', 100, 'X') y, level seq from dual connect by level < 1e6; 

--- insert more rows by:
-- insert into cbc_noise select * from cbc_noise;

commit;
  
create index cbc_noise#i#1 on cbc_noise(x, y);

exec dbms_stats.gather_table_stats(null, 'CBC_NOISE', cascade=> true);
Collect all meta info:

select segment_name, header_file, header_block, (header_block+1) root_block 
  from dba_segments where segment_name = 'TEST_TAB#R';

SEGMENT_NAME HEADER_FILE HEADER_BLOCK ROOT_BLOCK
------------ ----------- ------------ ----------
TEST_TAB#R          1548      2661258    2661259

select object_name, object_id, to_char(object_id, 'XXXXXXXX') id_hex, data_object_id, to_char(data_object_id, 'XXXXXXXX') did_hex 
from dba_objects where object_name in ('TEST_TAB', 'TEST_TAB#R');

OBJECT_NAME OBJECT_ID ID_HEX  DATA_OBJECT_ID DID_HEX
----------- --------- ------  -------------- -------
TEST_TAB     2459814 2588A6         2464078  25994E
TEST_TAB#R   2464076 25994C         2464077  25994D

select object_name, object_id, to_char(object_id, 'XXXXXXXX') id_hex, data_object_id, to_char(data_object_id, 'XXXXXXXX') did_hex 
from dba_objects where object_name in ('CBC_NOISE', 'CBC_NOISE#I#1');

OBJECT_NAME   OBJECT_ID ID_HEX  DATA_OBJECT_ID DID_HEX
------------- --------- ------  -------------- -------
CBC_NOISE       2464039 259927         2464067  259943
CBC_NOISE#I#1   2464043 25992B         2464066  259942  


2. Prepare Test


With above index root block number (2661259), we can find noise table index blocks which share the same CBC latch.

select * from (
with sq as (select object_name, data_object_id 
              from dba_objects 
             where object_name in ('TEST_TAB#R', 'CBC_NOISE#I#1')
            )
    ,bh as (select hladdr, obj, file#, dbablk, sum(tch) tch 
              from sys.x_bh group by hladdr, obj, file#, dbablk)
select hladdr cbc_latch_addr
      ,sum(tch) tch
      ,listagg(tch || '-' || obj || '(' || object_name || ')/' || file# || '/' ||dbablk, ';') 
         within group (order by tch desc) tch_list -- "tch-obj(name)/file/blk_list"
      ,count(*) blk_cnt
from  bh, sq
where bh.obj = sq.data_object_id
  and tch > 0
--  and (hladdr like '%18B6072A8%')   
group by hladdr
order by tch desc)
where 1=1
  and (tch_list like '%TEST_TAB#R%2661259%')
;

CBC_LATCH_ADDR   TCH TCH_LIST                                BLK_CNT
---------------- --- --------------------------------------  -------
000000018B6072A8  7  3-2464048(TEST_TAB#R)   /1548/2661259    3
                     2-2464066(CBC_NOISE#I#1)/1548/1482751
                     2-2464066(CBC_NOISE#I#1)/1548/1511408
The above output shows that latch 0x18B6072A8 protects TEST_TAB#R root block (2661259), and two CBC_NOISE#I#1 blocks (1482751, 1511408).

Take one CBC_NOISE#I#1 block, for example, 1482751, we can list all its indexed table rows.

select blk, x from (
  select x 
        ,dbms_rowid.rowid_block_number(sys_op_lbid (2464043, 'L', rowid)) blk
    from cbc_noise)  
where blk in (1482751);

       BLK          X
---------- ----------
   1482751     200656
   1482751     200657
   ...
   
60 rows selected. 
Now we insert 4905 rows into test_tab, that is exactly one row before index leaf block split:

truncate table test_tab;

insert into test_tab select level, -level from dual connect by level <= 4905;

commit;

-- using above index object_id to substitute for 2458815
select blk, count(*), min(id) min_id, max(id) max_id from (
  select id 
        ,dbms_rowid.rowid_block_number(sys_op_lbid (2464076, 'L', rowid)) blk
    from test_tab)  
where id between 1 and 11
group by blk order by min_id, blk;

      BLK COUNT(*) MIN_ID MAX_ID
  ------- -------- ------ ------
  2661263       11      1     11


3. Blocking Test


Open 3 Sqlplus sessions.


3.1 Session-1: Update cbc_noise


At first, start appended dtrace_stop.d to trace Session-1 on above shared CBC latch (same Dtrace script as Blog: Cache Buffer Chains Latch Contention Case Study-1: Reverse Key Index ).

Then in Session-1 (SPID 8438, SID 184), update one found row of cbc_noise, for example, x = 200656:

SQL (SPID 8438, SID 184) >  update cbc_noise set y = 'cbc_test' where x = 200656;
Session-1 is suspended, and here the Dtrace output:

oracle@testdb > ksh ./dtrace_stop.d 8438 0x18B6072A8

dtrace: allowing destructive actions
 CPU     ID                    FUNCTION:NAME
   2  81245       ksl_get_shared_latch:entry
---------Step 1 at 2020 Apr 5 06:33:03 (Elapsed-ns=1580535694)---------
***State Entry>>>(nproc_pid=>0, flag=>0, gets=>86515, latch=>228, level=>1, where=>2195)
>>>Transition>>>ksl_get_shared_latch:entry
      (Addr=>0x18B6072A8, Immed_Get=>1, Why=>0, Where=>2141(0x85D), Mode=>16, PID=>0x200000000000001A)
              oracle`ksl_get_shared_latch
              a.out`kcbget+0x3411
              a.out`ktbxchg+0x10c
              a.out`kdifind+0x504
              a.out`kdimod0+0x2360
              a.out`kauXuFinish+0x1a8
              a.out`kxibFinish+0x2bc
              oracle`qerupFetch+0x993
              a.out`qerstFetch+0x543
              oracle`updaul+0x92c
              oracle`updThreePhaseExe+158
              oracle`updexe+307
              oracle`opiexe+2c55
              oracle`kpoal8+a45

   2  81246      ksl_get_shared_latch:return
>>>State Return***(nproc_pid=>26, flag=>536870912, gets=>86516, latch=>228, level=>1, where=>2141)
   5      2                             :END
---------Step 2 at 2020 Apr 5 06:33:03 (Elapsed-ns=420179764)---------
Latch Gets = 0 (86515 - 86515)


(where=>2195: kcbgtcr: fast path pin
 where=>2141: kcbget: new pin)
By the way, above Call Stack shows call of subroutine "updThreePhaseExe", which is related to Oracle Write Consistency ("updaul" is "update Attempt Update of all rows or Lock all rows"). For full Call Stack, see Appendix: Session-1 (SID 184).


3.2 Session-2: Insert to Trigger Leaf Block Split and Root Block Modification


At first, start appended dtrace_kslgess.d to trace Session-2 on shared latch (same Dtrace script as Blog: Cache Buffer Chains Latch Contention Case Study-2: Reverse Primary Key Index).

Then in Session-2 (SPID 8668, SID 367), insert one row into test_tab. Immediately it is blocked by Session-1 in Wait Event: 'latch: cache buffers chains'.

SQL (SPID 8668, SID 367) >> insert into test_tab values(4906, -4906);
Here the Dtrace output:

oracle@testdb > ksh ./dtrace_kslgess.d 8668 0x18B6072A8

dtrace: allowing destructive actions
 CPU     ID                    FUNCTION:NAME
   0  81263                    kslgess:entry
---------Step 1 at 2020 Apr 5 06:33:43 (Elapsed-ns=19370786619)---------
***State Entry>>>(nproc_pid=>26, flag=>536870912, gets=>86516, latch=>228, level=>1, where=>2141)
>>>Transition>>>kslgess:entry
      (Addr=>0x18B6072A8, Where=>2196)
              oracle`kslgess
              oracle`kcbgcur+0xd45
              oracle`ktbgcur+0xb4
              a.out`kdifind+0x39a
              a.out`kdiins1+0xa3c
              a.out`kdiinsp0+0x7b
              a.out`kauxsin+0x799
              a.out`qesltcLoadIndexList+0x36e
              a.out`qerltcSimpleSingleInsRowCBK+0x5a
              a.out`qerltcSingleRowLoad+0x150


3.3 Session-3: Select Statement


In Session-3, we start one select on test_tab, and it is also blocked by Session-1 in Wait Event: 'latch: cache buffers chains'.

SQL (SPID 8670, SID 548) > select * from test_tab where id = 234;
Therefore any statements which require that root block access hit the contention at the same latch. But other sessions, which do not touch this CBC latch (directly or indirectly), are still running. In this case, we can observe some session working, some blocked.


4. System-Wide Blocking


Let system run with the above blocking for a while. One hour later, run a query on v$wait_chains, order them by blocking sequence (IN_WAIT_SECS). It shows SMON, DB Writer and other background processes also blocked in wait event: 'latch: cache buffers chains' or 'buffer busy waits' (file#=3 is UNDO file).

select (select s.program from v$session s where sid=w.sid) program
      ,chain_signature, osid, pid, sid
      ,blocker_is_valid bvalid, blocker_sid, p1, p1_text, in_wait_secs, num_waiters
  from v$wait_chains w
 order by in_wait_secs desc nulls first;


PROGRAM CHAIN_SIGNATURE                                    OSID  PID SID BVALID BLOCKER_SID P1         P1_TEXT IN_WAIT_SECS NUM_WAITERS
------- -------------------------------------------------- ----  --- --- ------ ----------- ---------- ------- ------------ -----------
sqlplus 'latch: cache buffers chains'<='buffer busy waits' 8438  26  184 FALSE                                                       7              
sqlplus 'latch: cache buffers chains'                      8668  25  367 TRUE   367         6633321128 address       6,286           0               
sqlplus 'latch: cache buffers chains'                      8670  27  548 TRUE   367         6633321128 address       6,257           0               
(DBW0)  'latch: cache buffers chains'                      15528 11  897 TRUE   367         6633321128 address       6,106           0               
(M001)  'latch: cache buffers chains'<='buffer busy waits' 12184 32  368 TRUE   367         6633321128 address       4,675           3               
(SMON)  'latch: cache buffers chains'<='buffer busy waits' 15540 15  541 TRUE   368         3          file#         4,375           0               
(W002)  'latch: cache buffers chains'<='buffer busy waits' 12186 23  903 TRUE   368         3          file#         4,374           0               
(M000)  'latch: cache buffers chains'<='buffer busy waits' 12474 36  5   TRUE   368         3          file#           245           0                 
P1 value "6633321128 = 0x18B6072A8" is the CBC latch address.

IN_WAIT_SECS indicates the blocking sequence.

NUM_WAITERS shows number of sessions blocked by this session. In above example, 4 are directly blocked, 3 indirectly blocked (blocking chain).

Appendex-3 lists their Call Stacks.


5. Blocking Objects and Blocks


To terminate the blocking, run command "prun 8438" to terminate the blocking.

With above shared latch address 0x18B6072A8, we can run a query to list its protected objects and blocks currently in use:

select * from (
with sq as (select object_name, data_object_id 
              from dba_objects 
             -- where object_name in ('TEST_TAB#R', 'CBC_NOISE#I#1')
            )
    ,bh as (select hladdr, obj, file#, dbablk, sum(tch) tch 
              from sys.x_bh group by hladdr, obj, file#, dbablk)
select hladdr cbc_latch_addr
      ,sum(tch) tch
      ,listagg(tch || '-' || obj || '(' || object_name || ')/' || file# || '/' ||dbablk, ';') 
         within group (order by tch desc) tch_list -- "tch-obj(name)/file/blk_list"
      ,count(*) blk_cnt
from  bh, sq
where bh.obj = sq.data_object_id
  and tch > 0
  and (hladdr like '%18B6072A8%')   
group by hladdr
order by tch desc)
;

CBC_LATCH_ADDR   TCH TCH_LIST                                BLK_CNT
---------------- --- --------------------------------------  -------
000000018B6072A8  9  2-2464089(TEST_TAB#R)    /1548/2661259    5
                     4-2464066(CBC_NOISE#I#1) /1548/1482751
                     1-8      (C_FILE#_BLOCK#)/1/111481    
                     1-8      (SEG$)          /1/111481    
                     1-8      (UET$)          /1/111481    
We can see the system objects (C_FILE#_BLOCK#, SEG$, UET$) under this latch. Hence all sessions which access those object blocks hit the same CBC contention.

In summary, this Blog is trying to manually hold a CBC latch to simulate CBC latch contention. It demonstrated that one unrelated DML can block other index root (branch) operations by CBC latch, and even has system-wide impacts.


6. Index Root Block CBC Latch Contention Test


Similar to two previous Blog: Cache Buffer Chains Latch Contention Case Study-1: Reverse Key Index and Cache Buffer Chains Latch Contention Case Study-2: Reverse Primary Key Index, we start 12 Job sessions to access test_tab:

exec test_tab_select_job(6, 1e9, 1, 0);

exec test_tab_select_job(6, 1e9, 6, 0);

-- stop all jobs by:
--   exec clean_jobs;
Then open two Sqlplus sessions, one updates cbc_noise in a loop, another selects any one row from test_tab in a loop.

begin
  for i in 1..1e9 loop
    update cbc_noise set y = 'cbc_test' where x = 200656;
  end loop;
end;
/

begin
  for i in 1..1e9 loop
    for c in (select * from test_tab where id = 234) loop
      null;
    end loop;
  end loop;
end;
/
We can observe the heavy CBC latch contention on latch child: '000000018B6072A8', which protects test_tab index root block and the updated cbc_noise index block (and also other blocks). In real applications, it implies that a sporadic heavy DML on an unrelated table can affect other running sessions, and makes the performance issues hard to track / reproduce.

select s.program, s.client_identifier, s.sid, s.serial#, p.pid, p.spid, s.event, p1, p2, p3, latchwait, latchspin
  from v$session s, v$process p
 where s.paddr=p.addr and (lower(s.program) like '%j0%' or lower(s.program) like '%sql%') 
 order by s.client_identifier; 

  PROGRAM               CLIENT_IDENTIFIER     SID  SERIAL#  PID  SPID   EVENT                        P1          P2   P3
  --------------------  --------------------  ---  -------  ---  ----   ---------------------------  ----------  ---  --
  oracle@testdb (J000)  test_tab_select_id_1  369  58913    26   21665  latch: cache buffers chains  6633321128  228   0 
                        ...                                                                                            
  oracle@testdb (J006)  test_tab_select_id_6  546  11249    39   21675  latch: cache buffers chains  6633321128  228   0  
                        ...                                                                                            
  sqlplus.exe                                 727  61279    46   21740  latch: cache buffers chains  6633321128  228   0
  sqlplus.exe                                 729  40349    44   21917  latch: cache buffers chains  6633321128  228   0 


select * from v$latch_misses 
 where parent_name = 'cache buffers chains' and sleep_count > 0
 order by sleep_count desc;

  WHERE                     NWFAIL_COUNT  SLEEP_COUNT  WTR_SLP_COUNT  LONGHOLD_COUNT  LOCATION
  ------------------------  ------------  -----------  -------------  --------------  -----------------------
  kcbgtcr: fast path exam   0             5283         4592           840             kcbgtcr: fast path exam
  kcbso1: set no access     0             477          1236           18              kcbso1: set no access  
  kcbgtcr: fast path pin    0             66           34             22              kcbgtcr: fast path pin 
  kcbgcur: fast path excl   0             38           11             24              kcbgcur: fast path excl
  kcbget: new pin           0             33           0              21              kcbget: new pin        
  kcbchg: cr pin change     0             10           2              5               kcbchg: cr pin change  
  kcbchg: change complete   0             5            8              4               kcbchg: change complete

 
select * from v$latchholder;

  PID  SID  LADDR             NAME                  GETS
  ---  ---  ----------------  --------------------  ----------
  24   10   000000018B6072A8  cache buffers chains  142727994

select latch#, child#, addr, gets, misses, sleeps, spin_gets, wait_time, round(wait_time/sleeps) us_per_sleep 
  from v$latch_children 
 where addr = hextoraw('000000018B6072A8'); 

  LATCH#  CHILD#  ADDR              GETS       MISSES    SLEEPS   SPIN_GETS  WAIT_TIME    US_PER_SLEEP
  ------  ------  ----------------  ---------  --------  -------  ---------  ----------   ------------
  228     15672   000000018B6072A8  191753377  1460415   6280     1454291    27969528879  4453747


7. Appendix



7.1 dtrace_stop.d



sudo dtrace -w -n \
'
typedef unsigned int ub4;

typedef struct latchstate /* latch state */ {
    ub4 nproc_pid;  /* Nproc or pid   */
    ub4 flag;       /* blocking flag  */
    ub4 gets;       /* number of gets */
    ub4 latch;      /* latch number   */
    ub4 level;      /* latch level    */
    ub4 where; } latchstate;

BEGIN {laddr = $1; step = 1; begin_gets = 0, end_gets = 0; prev_ts = timestamp;
   self->evt = "begin"; self->loc = 0x0;}
pid$target::ksl_get_shared_latch:entry /arg0 == laddr && (self->evt != probefunc || self->loc != arg3)/ {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   ls = ((latchstate *) copyin(arg0, sizeof(latchstate)));
   begin_gets = (begin_gets == 0 ? ls->gets: begin_gets); end_gets = ls->gets;
   printf("\n***State Entry>>>(nproc_pid=>%d, flag=>%d, gets=>%d, latch=>%d, level=>%d, where=>%d)"
           ,ls->nproc_pid, ls->flag, ls->gets, ls->latch, ls->level, ls->where);
   printf("\n>>>Transition>>>%s:%s\n      (Addr=>0x%X, Immed_Get=>%d, Why=>%d, Where=>%d(0x%X), Mode=>%d, PID=>0x%X)",
          probefunc, probename, arg0, arg1, arg2, arg3, arg3, arg4, arg5);
   self->evt = probefunc; self->loc = arg3;
   ustack(14, 0);
   /* stop(); exit(0); */    /* no latch blocking, failed get, try again */
   step = step + 1; self->entry = 1; prev_ts = timestamp;}
pid$target::ksl_get_shared_latch:return /self->entry == 1/ {
   ls = ((latchstate *) copyin(laddr, sizeof(latchstate)));
   printf("\n>>>State Return***(nproc_pid=>%d, flag=>%d, gets=>%d, latch=>%d, level=>%d, where=>%d)"
           ,ls->nproc_pid, ls->flag, ls->gets, ls->latch, ls->level, ls->where);
   stop(); exit(0);      /* simulate long latch holder, run by: dtrace -w -n */
   self->entry = 2;}
pid$target::kslfre:entry /self->entry == 2 && arg0 == laddr && self->evt != probefunc/ {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   ls = ((latchstate *) copyin(arg0, sizeof(latchstate)));
   end_gets = ls->gets;
   printf("\n>>>Transition>>>%s:%s(Addr=>0x%-X)", probefunc, probename, arg0);
   self->evt = probefunc; self->loc = 0x0;
   /* stop(); exit(0); */      /* simulate long latch holder, run by: dtrace -w -n */
   printf("\n===Latch freed===");
   ustack(10, 0); step = step + 1; prev_ts = timestamp;}
END {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   printf("\nLatch Gets = %d (%d - %d)", (end_gets - begin_gets), (unsigned long)begin_gets, (unsigned long)end_gets); }
' -p $1 $2


7.2 dtrace_kslgess.d



sudo dtrace -w -n \
'
typedef unsigned int ub4;

typedef struct latchstate /* latch state */ {
    ub4 nproc_pid;  /* Nproc or pid   */
    ub4 flag;       /* blocking flag  */
    ub4 gets;       /* number of gets */
    ub4 latch;      /* latch number   */
    ub4 level;      /* latch level    */
    ub4 where; } latchstate;

BEGIN {laddr = $1; step = 1; begin_gets = 0, end_gets = 0; prev_ts = timestamp;
   self->evt = "begin"; self->loc = 0x0;}
pid$target::kslgess:entry /arg0 == laddr && (self->evt != probefunc || self->loc != arg4)/ {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   ls = ((latchstate *) copyin(arg0, sizeof(latchstate)));
   begin_gets = (begin_gets == 0 ? ls->gets: begin_gets); end_gets = ls->gets;
   printf("\n***State Entry>>>(nproc_pid=>%d, flag=>%d, gets=>%d, latch=>%d, level=>%d, where=>%d)"
           ,ls->nproc_pid, ls->flag, ls->gets, ls->latch, ls->level, ls->where);
   printf("\n>>>Transition>>>%s:%s\n      (Addr=>0x%X, Where=>%d)",
          probefunc, probename, arg0, arg4);
   self->evt = probefunc; self->loc = arg4;
   ustack(10, 0);
   /*stop(); exit(0);*/    /* no latch blocking, failed get, try again */
   step = step + 1; self->entry = 1; prev_ts = timestamp;}
pid$target::kslgess:return /self->entry == 1/ {
   ls = ((latchstate *) copyin(laddr, sizeof(latchstate)));
   printf("\n>>>State Return***(nproc_pid=>%d, flag=>%d, gets=>%d, latch=>%d, level=>%d, where=>%d)"
           ,ls->nproc_pid, ls->flag, ls->gets, ls->latch, ls->level, ls->where);
   /*stop(); exit(0);*/    /* simulate long latch holder, run by: dtrace -w -n */
   self->entry = 2;}
pid$target::kslfre:entry /self->entry == 2 && arg0 == laddr && self->evt != probefunc/ {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   ls = ((latchstate *) copyin(arg0, sizeof(latchstate)));
   end_gets = ls->gets;
   printf("\n>>>Transition>>>%s:%s(Addr=>0x%-X)", probefunc, probename, arg0);
   self->evt = probefunc; self->loc = 0x0;
   /*stop(); exit(0);*/      /* simulate long latch holder, run by: dtrace -w -n */
   printf("\n===Latch freed===");
   ustack(10, 0); step = step + 1; prev_ts = timestamp;}
END {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   printf("\nLatch Gets = %d (%d - %d)", (end_gets - begin_gets), (unsigned long)begin_gets, (unsigned long)end_gets); }
' -p $1 $2


7.3 Call Stacks



7.3.1 Session-1 (SID 184) 'latch: cache buffers chains'<='buffer busy waits'



oracle@testdb > pstack 8438
 8438:   testdb (LOCAL=NO)
 ------------  lwp# 1 / thread# 1  ---------------
  00000000059e3711 kcbget () + 3411
  00000000059e01cc ktbxchg () + 10c
  00000000059de834 kdifind () + 504
  00000000059d70e0 kdimod0 () + 2360
  000000000d986c88 kauXuFinish () + 1a8
  000000000f5485ac kxibFinish () + 2bc
  0000000005729233 qerupFetch () + 993
  00000000092cc193 qerstFetch () + 543
  00000000057234ec updaul () + 92c
  0000000005720428 updThreePhaseExe () + 158
  000000000571f6d7 updexe () + 307
  0000000005707995 opiexe () + 2c55
  0000000005c73275 kpoal8 () + a45
  00000000056f87e3 opiodr () + 433
  0000000005701323 ttcpip () + 593
  00000000056fd59c opitsk () + 6cc
  00000000056fc405 opiino () + 3c5
  00000000056f87e3 opiodr () + 433
  00000000056f7dcb opidrv () + 33b
  00000000056f79fa sou2o () + 7a
  00000000056f7775 opimai_real () + 265
  00000000056f70be ssthrdmain () + 22e
  00000000056f6e64 main () + a4


7.3.2 Session-2 (SID 367) 'latch: cache buffers chains'



----- without using dtrace_kslgess.d, only use pstack

oracle@testdb > pstack 8668
 8668:   testdb (LOCAL=NO)
 ------------  lwp# 1 / thread# 1  ---------------
  ffff80ffbbf93d4b semsys   (2, 27, ffff80ffbffe21e8, 1, a45)
  000000000578adc5 sskgpwwait () + 1e5
  000000000578a965 skgpwwait () + c5
  000000000578972d kslgess () + 92d
  00000000057a9be5 kcbgcur () + d45
  00000000057eecb4 ktbgcur () + b4
  00000000059de6ca kdifind () + 39a
  0000000005a224fc kdiins1 () + a3c
  0000000005a21a5b kdiinsp0 () + 7b
  0000000005a1e269 kauxsin () + 799
  0000000005a1c42e qesltcLoadIndexList () + 36e
  0000000005ae06fa qerltcSimpleSingleInsRowCBK () + 5a
  0000000005a1b210 qerltcSingleRowLoad () + 150
  0000000005a19f36 qerltcFetch () + 1d6
  00000000092cc11a qerstFetch () + 4ca
  0000000005a19297 insexe () + 407
  00000000057064df opiexe () + 179f
  0000000005c73275 kpoal8 () + a45
  00000000056f87e3 opiodr () + 433
  0000000005701323 ttcpip () + 593
  00000000056fd59c opitsk () + 6cc
  00000000056fc405 opiino () + 3c5
  00000000056f87e3 opiodr () + 433
  00000000056f7dcb opidrv () + 33b
  00000000056f79fa sou2o () + 7a
  00000000056f7775 opimai_real () + 265
  00000000056f70be ssthrdmain () + 22e
  00000000056f6e64 main () + a4


7.3.3 oracle@testdb (DBW0 SID 897) 'latch: cache buffers chains'



oracle@testdb > pstack 15528 
 15528:  ora_dbw0_testdb
 ------------  lwp# 1 / thread# 1  ---------------
  ffff80ffbbf93d4b semsys   (2, 27, ffff80ffbfff88a8, 1, 623f7)
  000000000578adc5 sskgpwwait () + 1e5
  000000000578a965 skgpwwait () + c5
  000000000578972d kslgess () + 92d
  00000000057889bf ksl_get_shared_latch () + 39f
  00000000066154ec kcbbxsv () + 2fc
  00000000066119f9 kcbb_coalesce () + 149
  0000000008c389d5 kcbbwdl () + 655
  000000000660b716 kcbbdrv () + df6
  00000000065d9443 ksbcti () + 583
  00000000065cfadd ksbabs () + 91d
  00000000065cde36 ksbrdp () + 6c6
  000000000627c9fc opirip () + 34c
  00000000056f7d1b opidrv () + 28b
  00000000056f79fa sou2o () + 7a
  00000000056f764d opimai_real () + 13d
  00000000056f70be ssthrdmain () + 22e
  00000000056f6e64 main () + a4


7.3.4 oracle@testdb (SMON SID 541)'latch: cache buffers chains'<='buffer busy waits'



oracle@testdb > pstack 15540
 15540:  ora_smon_testdb
  ffff80ffbbf93d4b semsys   (4, 27, ffff80ffbfff70c8, 1, ffff80ffbfff70d0)
  000000000578acd8 sskgpwwait () + f8
  000000000578a965 skgpwwait () + c5
  0000000005944ffc ksliwat () + 8dc
  0000000005944350 kslwaitctx () + 90
  0000000005955d2d kcbzwb () + 86d
  00000000057aeadc kcbgcur () + 5c3c
  00000000057a551a ktuGetUsegHdr () + 3da
  00000000067ba665 kturRecoverUndoSegment () + 1f5
  00000000067b87e3 kturRecoverActiveTxns1 () + 4d3
  00000000067b8252 kturRecoverActiveTxns () + 22
  00000000067b6697 ktprbeg1 () + 177
  00000000067b645f ktprbeg () + 1f
  00000000067b16fc ktmmon () + fbc
  00000000067afe94 ktmSmonMain () + f4
  00000000065cde36 ksbrdp () + 6c6
  000000000627c9fc opirip () + 34c
  00000000056f7d1b opidrv () + 28b
  00000000056f79fa sou2o () + 7a
  00000000056f764d opimai_real () + 13d
  00000000056f70be ssthrdmain () + 22e
  00000000056f6e64 main () + a4


7.3.5 oracle@testdb (W002 SID 903) 'latch: cache buffers chains'<='buffer busy waits'



oracle@testdb > pstack 12186
 12186:  ora_w002_testdb
  ffff80ffbbf93d4b semsys   (4, 27, ffff80ffbfff5e88, 1, ffff80ffbfff5e90)
  000000000578acd8 sskgpwwait () + f8
  000000000578a965 skgpwwait () + c5
  0000000005944ffc ksliwat () + 8dc
  0000000005944350 kslwaitctx () + 90
  0000000005955d2d kcbzwb () + 86d
  00000000057aeadc kcbgcur () + 5c3c
  00000000057a551a ktuGetUsegHdr () + 3da
  0000000005ae819a ktuGetExtTxnInfo () + 2ca
  00000000062131dd ktuxcm () + 9d
  00000000057a6f9d ktecgetsh () + 67d
  00000000057a6903 ktecgshx () + 23
  0000000005c39c26 kteinicnt3 () + 2e6
  0000000005c3992c kteinicnt1 () + 2c
  0000000008dea761 ktssupd_segment_extblks_bkg () + 511
  0000000006287799 ktsj_task_switch () + 149
  0000000006286100 ktsj_execute_task () + 2b0
  0000000006284d6e ktsj_slave_main () + 42e
  000000000627ddcd ksvrdp () + aed
  000000000627c9ab opirip () + 2fb
  00000000056f7d1b opidrv () + 28b
  00000000056f79fa sou2o () + 7a
  00000000056f764d opimai_real () + 13d
  00000000056f70be ssthrdmain () + 22e
  00000000056f6e64 main () + a4


7.3.6 oracle@testdb (M001 SID 368) 'latch: cache buffers chains'<='buffer busy waits'



oracle@testdb > pstack 12184
 12184:  ora_m001_testdb
 ------------  lwp# 1 / thread# 1  ---------------
  ffff80ffbbf93d4b semsys   (2, 27, ffff80ffbffb28a8, 1, f6)
  000000000578adc5 sskgpwwait () + 1e5
  000000000578a965 skgpwwait () + c5
  000000000578972d kslgess () + 92d
  00000000057889bf ksl_get_shared_latch () + 39f
  000000000579b3bc kcbnew () + 3c9c
  00000000057957c7 ktugnb () + 327
  0000000005790ad8 ktugur () + a08
  000000000574359e ktuchg2 () + 49e
  0000000005743007 ktbchg2 () + 1d7
  0000000005a81c6b kdisle () + 3ed3b
  0000000005a25ae3 kdiins1 () + 4023
  0000000005a1e8fc kauxsin () + e2c
  0000000005a1c659 qesltcLoadIndexList () + 599
  0000000005afff45 qerltcFlushKdtBuffer () + 225
  0000000005b2e1c4 qerltcBufRow () + 74
  0000000005b2dbd2 qerltcKdtBufferedInsRowCBK () + 152
  0000000005afea47 qerltcLoadStateMachine () + 237
  0000000005fd71b1 qerltcInsertSelectRop () + 101
  00000000092cb832 qerstRowP () + 412
  00000000092cb832 qerstRowP () + 412
  0000000005d1c2f3 qerfxFetch () + c73
  00000000092cc11a qerstFetch () + 4ca
  0000000005c78d3f rwsfcd () + 6f
  00000000092cc11a qerstFetch () + 4ca
  0000000005a1a211 qerltcFetch () + 4b1
  00000000092cc11a qerstFetch () + 4ca
  0000000005a19297 insexe () + 407
  00000000057064df opiexe () + 179f
  0000000005c73275 kpoal8 () + a45
  00000000056f87e3 opiodr () + 433
  0000000005de3542 kpoodrc () + 22
  000000000571a07d rpiswu2 () + 2fd
  0000000005de3187 kpoodr () + 287
  0000000005de272b upirtrc () + e0b
  0000000005de18e0 kpurcsc () + 70
  0000000005ddc2da kpuexec () + 1b0a
  0000000005dda7bb OCIStmtExecute () + 2b
  0000000005f1914f kewrgwxf1_gwrsql_exft_1 () + 3df
  0000000005f180bb kewrgwxf_gwrsql_exft () + 23b
  0000000009aabb65 kewrft_flush_table () + 4e5
  0000000009aaaeab kewrftec_flush_table_ehdlcx () + c7b
  0000000009ab9b3a kewrfat1_flush_all_tables_1 () + 42a
  0000000009aba3ec kewrfat_flush_all_tables () + 3ec
  0000000009abb5a6 kewrfop_flush_one_phase () + 226
  0000000009abbedb kewrfap_flush_all_phases () + 7b
  0000000009aa5eb2 kewrfos_flush_onesnap () + bf2
  0000000009aa6b14 kewrfsc_flush_snapshot_c () + 564
  0000000009aa4ad0 kewrcmdbs_flush_mdb_snap () + 460
  0000000009af4283 kewrafs_auto_flush_slave () + 6e3
  000000000633455c kebm_slave_main () + 29c
  000000000627ddcd ksvrdp () + aed
  000000000627c9ab opirip () + 2fb
  00000000056f7d1b opidrv () + 28b
  00000000056f79fa sou2o () + 7a
  00000000056f764d opimai_real () + 13d
  00000000056f70be ssthrdmain () + 22e
  00000000056f6e64 main () + a4

Tuesday, February 25, 2020

Cache Buffer Chains Latch Contention Case Study-2: Reverse Primary Key Index

Continue with previous Blog: Cache Buffer Chains Latch Contention Case Study-1: Reverse Key Index, now we are going to make the second Case Study: Reverse Primary Key Index. It shows the diligent work of Oracle optimization.

Note: Tested in Oracle 12c, 18c, 19c.


1. Test Setup


First we drop the previous reverse index and create a new reverse primary key index, then get object_id of that index:

--drop table test_tab purge;
--create table test_tab (id number, val number);

drop index test_tab#r;

create unique index test_tab#r on test_tab(id) reverse;

alter table test_tab add constraint test_tab#r primary key (id);

select object_id, to_char(object_id, 'XXXXXXXX') id_hex, data_object_id, to_char(data_object_id, 'XXXXXXXX') did_hex 
from dba_objects where object_name = 'TEST_TAB#R';

  OBJECT_ID ID_HEX DATA_OBJECT_ID DID_HEX
  --------- ------ -------------- -------
    2460326 258AA6        2460326  258AA6


2. Leaf Block Split Point Finding


With appended Plsql procedure: leaf_split_finder, we can find one exact point of leaf block split.

SQL > exec leaf_split_finder(5000);

--- Leaf Split at row count: 4906, (id between 1 and 11) in 2 Blocks
block: 2661262, cnt: 5, min_id: 1, max_id: 5
block: 3665613, cnt: 6, min_id: 6, max_id: 11
Above output shows index split occurs at row count: 4906. In the previous Blog, that is 4708. So primary key index is more compact (4.2%) than normal index.


3. CBC Latch Contention Producing


Open a Plsql Window, truncate table, insert 4905 rows at first.

truncate table test_tab;

insert into test_tab select level, -level from dual connect by level <= 4905;

commit;

-- using above index object_id to substitute for 2460326
select blk, count(*), min(id) min_id, max(id) max_id from (
  select id 
        ,dbms_rowid.rowid_block_number(sys_op_lbid (2460326, 'L', rowid)) blk
    from test_tab)  
where id between 1 and 11
group by blk order by min_id, blk;

      BLK COUNT(*) MIN_ID MAX_ID
  ------- -------- ------ ------
  2661263       11      1     11
We can see all rows from 1 to 11 are in the same leaf block (2661263).

Now we start two groups of jobs, each group having 6 jobs in a query loop (see appended test_tab_select_job). All 6 jobs in the first group access row 1 to 5; whereas 6 jobs in the second group access row 6 to 10;

exec test_tab_select_job(6, 1e9, 1, 0);

exec test_tab_select_job(6, 1e9, 6, 0);

-- stop all jobs by:
--   exec clean_jobs;
Each job in the first group executes the query with id from 1 to 5 in a loop, whereas job in the second group executes it with id from 6 to 10. Both are with the same Xplan (INDEX UNIQUE SCAN. In previous Blog, it is INDEX RANGE SCAN).

select val from test_tab where id = :B1
 
------------------------------------------------------------------------------------------
| Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |            |       |       |     1 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST_TAB   |     1 |    26 |     1   (0)| 00:00:01 |
|*  2 |   INDEX UNIQUE SCAN         | TEST_TAB#R |     1 |       |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------------
Now insert a single new row:

insert into test_tab values(4906, -4906);

select blk, count(*), min(id) min_id, max(id) max_id from (
  select id 
        ,dbms_rowid.rowid_block_number(sys_op_lbid (2460326, 'L', rowid)) blk
    from test_tab)  
where id between 1 and 11
group by blk order by min_id, blk;

      BLK COUNT(*) MIN_ID MAX_ID
  ------- -------- ------ ------
  2661263        5      1      5
  3665609        6      6     11
One leaf block split occurs. Row 6 to 11 are moved to new block (3665609). Now if we look again all 12 job sessions. Only 6 sessions in the second group have contention, similar to the case of previous Blog.

select s.client_identifier, s.sid, s.serial#, p.pid, p.spid, s.event, p1, p2, p3, latchwait, latchspin
  from v$session s, v$process p
 where s.paddr=p.addr and lower(s.program) like '%j0%' 
 order by s.client_identifier; 

  CLIENT_IDENTIFIER     SID  SERIAL#  PID  SPID  EVENT                        P1          P2       P3  LATCHWAIT         LATCHSPIN
  --------------------  ---  -------  ---  ----  ---------------------------  ----------  -------  --  ----------------  ---------
  test_tab_select_id_1  725  53859    46   5226  buffer busy waits            1548        2661259  1
  test_tab_select_id_1  549  449      51   5236  null event                   0           0        0
  test_tab_select_id_1  547  35779    45   5224  buffer busy waits            1548        2661263  1
  test_tab_select_id_1  6    60174    42   5218  buffer busy waits            1548        2661263  1
  test_tab_select_id_1  8    49157    48   5230  buffer busy waits            1548        2661263  1
  test_tab_select_id_1  187  44089    43   5220  null event                   0           0        0
  test_tab_select_id_6  727  18689    52   5238  latch: cache buffers chains  6636068072  228      0   000000018B8A5CE8
  test_tab_select_id_6  369  18373    50   5234  latch: cache buffers chains  6636068072  228      0   000000018B8A5CE8
  test_tab_select_id_6  189  58221    49   5232  latch: cache buffers chains  6636068072  228      0   000000018B8A5CE8
  test_tab_select_id_6  905  10131    53   5240  latch: cache buffers chains  6636068072  228      0   000000018B8A5CE8
  test_tab_select_id_6  367  49253    44   5222  latch: cache buffers chains  6636068072  228      0
  test_tab_select_id_6  903  64294    47   5228  latch: cache buffers chains  6636068072  228      0   000000018B8A5CE8  
A further query on v$latch_children shows majority of CBC SLEEPS (140174) is on child latch: 000000018B8A5CE8 of second group. The child latch: 000000018B8DC5E8 of first group almost has no SLEEPS and WAIT_TIME.

select latch#, child#, addr, gets, misses, sleeps, spin_gets, wait_time, round(wait_time/sleeps) us_per_sleep 
  from v$latch_children 
 where addr = hextoraw('000000018B8DC5E8') or addr = hextoraw('000000018B8A5CE8'); 

  LATCH#  CHILD#  ADDR              GETS       MISSES    SLEEPS   SPIN_GETS  WAIT_TIME  US_PER_SLEEP
  ------  ------  ----------------  ---------  --------  -------  ---------  ---------  ------------
  228     31143   000000018B8DC5E8  57352278   59783     4       59779      81         20
  228     29979   000000018B8A5CE8  104090722  28359401  140174  28213248   334439191  2386  
With one more query on v$latch_misses, we can display CBC latch contention locations.

select * from v$latch_misses 
 where parent_name = 'cache buffers chains' and sleep_count > 0
 order by sleep_count desc;
 
  PARENT_NAME           WHERE                     NWFAIL_COUNT  SLEEP_COUNT  WTR_SLP_COUNT  LONGHOLD_COUNT  LOCATION
  --------------------  ------------------------  ------------  -----------  -------------  --------------  -----------------------
  cache buffers chains  kcbgtcr: slow path excl    0             181709       24123          2864            kcbgtcr: slow path excl
  cache buffers chains  kcbgtcr: fast path exam    0             61093        160708         10363           kcbgtcr: fast path exam
  cache buffers chains  kcbgkcbcr                  0             8999         65758          1806            kcbgkcbcr
  cache buffers chains  kcbrls: pin release        0             7171         10946          1580            kcbrls: pin release
  cache buffers chains  kcb_trim_hash_chain        0             2191         19             30              kcb_trim_hash_chain
  cache buffers chains  kcbchg: cr pin cleanout    0             444          82             43              kcbchg: cr pin cleanout
  cache buffers chains  kcbcge                     0             3            54             1               kcbcge
  cache buffers chains  kcbchg: cr pin change      0             3            0              0               kcbchg: cr pin change
  cache buffers chains  kcbzwb                     0             1            0              0               kcbzwb
  cache buffers chains  kcbgtcr: retry after read  0             1            0              0               kcbgtcr: retry after read 
The top Location are "kcbgtcr: slow path excl" and "kcbgtcr: fast path exam". "kcbgtcr: slow path excl" has higher SLEEP_COUNT, but "kcbgtcr: fast path exam" has much higher WTR_SLP_COUNT (Number of times a waiter slept) and LONGHOLD_COUNT (Number of times someone held a latch for the entire duration of someone else's sleep).

In the previous Blog for normal reverse index, the top Location is "kcbgtcr: fast path pin".

LONGHOLD_COUNT is a parmountal indicator for latch misses since it signifies the sleeping sessions can go to sleep again ("Recurrent Sleeps", see Blog: Oracle Latch Misses, Spin_gets, Sleeps and Recurrent_Misses ). But it is not included in AWR - "Latch Miss Sources" section.

If we make an index TEST_TAB#R tree dump, we can see two leaf blocks: 2661263 and 3665609:

alter session set tracefile_identifier = "TD_TEST_TAB#R_1";
alter session set events 'immediate trace name treedump level 2460326';

----- begin tree dump
branch: 0x13589b 1267867 (0: nrow: 16, level: 1)
   leaf: 0x289b8f 2661263 (-1: row:291.291 avs:3932)
   leaf: 0x37eec9 3665609 (0: row:282.282 avs:4060)
   leaf: 0x1358a1 1267873 (1: row:295.295 avs:3878)
Above dump shows that index root node block is 1267867. With following query, we can see that it is the next block of header_block: 1267866 on index TEST_TAB#R segment:

SQL > select segment_name, header_file, header_block from dba_segments where segment_name = 'TEST_TAB#R';

  SEGMENT_NAME HEADER_FILE HEADER_BLOCK
  ------------ ----------- ------------
  TEST_TAB#R            22      1267866
Even though there is heavy "latch: cache buffers chains" contention, it seems that no blocking sessions are tracked for such wait event:

SQL > select * from v$wait_chains where chain_signature like 'latch: cache buffers chains' or blocker_sid is not null;

      no rows selected


4. Latch State Transition Diagram By Dtrace


Pick one CBC latch contention session (SID: 367, Oracle PID: 44 (0x2C), spid: 5222) from above v$session and v$process query.

Run following Dtrace Script on contention latch address: 0x18B8A5CE8 (For Latch State Transition Diagram, See Blog: PL/SQL Function Result Cache Invalidation (I)).

sudo dtrace -w -n \
'
typedef unsigned int ub4;

typedef struct latchstate /* latch state */ {
    ub4 nproc_pid;  /* Nproc or pid   */
    ub4 flag;       /* blocking flag  */ 
    ub4 gets;       /* number of gets */
    ub4 latch;      /* latch number   */
    ub4 level;      /* latch level    */
    ub4 where; } latchstate;
    
BEGIN {laddr = $1; step = 1; begin_gets = 0, end_gets = 0; prev_ts = timestamp;
   self->evt = "begin"; self->loc = 0x0;}
pid$target::kslgess:entry /arg0 == laddr && (self->evt != probefunc || self->loc != arg4)/ { 
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   ls = ((latchstate *) copyin(arg0, sizeof(latchstate)));
   begin_gets = (begin_gets == 0 ? ls->gets: begin_gets); end_gets = ls->gets;
   printf("\n***State Entry>>>(nproc_pid=>%d, flag=>%d, gets=>%d, latch=>%d, level=>%d, where=>%d)"
           ,ls->nproc_pid, ls->flag, ls->gets, ls->latch, ls->level, ls->where);
   printf("\n>>>Transition>>>%s:%s\n      (Addr=>0x%X, Where=>%d)", 
          probefunc, probename, arg0, arg4);
   self->evt = probefunc; self->loc = arg4;    
   ustack(10, 0); 
   /*stop(); exit(0);*/    /* no latch blocking, failed get, try again */
   step = step + 1; self->entry = 1; prev_ts = timestamp;} 
pid$target::kslgess:return /self->entry == 1/ {
   ls = ((latchstate *) copyin(laddr, sizeof(latchstate))); 
   printf("\n>>>State Return***(nproc_pid=>%d, flag=>%d, gets=>%d, latch=>%d, level=>%d, where=>%d)"
           ,ls->nproc_pid, ls->flag, ls->gets, ls->latch, ls->level, ls->where);
   /*stop(); exit(0);*/    /* simulate long latch holder, run by: dtrace -w -n */
   self->entry = 2;}   
pid$target::kslfre:entry /self->entry == 2 && arg0 == laddr && self->evt != probefunc/ {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   ls = ((latchstate *) copyin(arg0, sizeof(latchstate)));
   end_gets = ls->gets;
   printf("\n>>>Transition>>>%s:%s(Addr=>0x%-X)", probefunc, probename, arg0);
   self->evt = probefunc; self->loc = 0x0;
   /*stop(); exit(0);*/      /* simulate long latch holder, run by: dtrace -w -n */
   printf("\n===Latch freed===");
   ustack(10, 0); step = step + 1; prev_ts = timestamp;}      
END {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   printf("\nLatch Gets = %d (%d - %d)", (end_gets - begin_gets), (unsigned long)begin_gets, (unsigned long)end_gets); }
' -p 5222 0x18B8A5CE8
The output is some repeated occurrences of following pattern:

   kslgess:entry
---------Step 1825 at 2020 Feb 24 15:51:09 (Elapsed-ns=6088)---------
***State Entry>>>(nproc_pid=>47, flag=>536870912, gets=>461619058, latch=>228, level=>1, where=>2168)
>>>Transition>>>kslgess:entry
      (Addr=>0x18B8A5CE8, Where=>2218)
              oracle`kslgess
              a.out`kcbgtcr+0x1a323
              a.out`ktrgtc2+0x316
              a.out`qeilbk1+0x4a1
              a.out`qeilbk0+0xe
              a.out`qerixFetchUniqueIndex+0x17f
              a.out`qerstFetch+0x543
              a.out`qertbFetchByRowID+0x39e
              a.out`qerstFetch+0x4ca
              a.out`opifch2+0x188b

   kslgess:return
>>>State Return***(nproc_pid=>44, flag=>536870912, gets=>461619062, latch=>228, level=>1, where=>2218)

   kslgess:entry
---------Step 1826 at 2020 Feb 24 15:51:09 (Elapsed-ns=250117)---------
***State Entry>>>(nproc_pid=>43, flag=>536870912, gets=>461619113, latch=>228, level=>1, where=>2186)
>>>Transition>>>kslgess:entry
      (Addr=>0x18B8A5CE8, Where=>2186)
              oracle`kslgess
              oracle`kcbrls+0x170c
              a.out`ktrgtc2+0x554
              a.out`qeilbk1+0x4a1
              a.out`qeilbk0+0xe
              a.out`qerixFetchUniqueIndex+0x17f
              a.out`qerstFetch+0x543
              a.out`qertbFetchByRowID+0x39e
              a.out`qerstFetch+0x4ca
              a.out`opifch2+0x188b
It shows the CBC Latch State Transition. At first, C subroutine kslgess was called to get CBC latch in entry location (where=>2168) and next location (where=>2186). Then freed by kslfre. CallStack shows that each row is accessed by qertbFetchByRowID via qerixFetchUniqueIndex (TABLE ACCESS BY INDEX ROWID via INDEX UNIQUE SCAN).

For the where location, we can use the query below to find them.

CREATE OR REPLACE FORCE VIEW X_GV$LATCH_MISSES as
SELECT t1.indx              INDX,
       t1.inst_id           INST_ID,       
       t1.ksllasnam         PARENT_NAME,   
       t2.ksllwnam          "WHERE",       
       t1.kslnowtf          NWFAIL_COUNT,  
       t1.kslsleep          SLEEP_COUNT,   
       t1.kslwscwsl         WTR_SLP_COUNT, 
       t1.kslwsclthg        LONGHOLD_COUNT,
       t2.ksllwnam          LOCATION,      
       t1.con_id            CON_ID         
  FROM x$ksllw t2, x$kslwsc t1
 WHERE t2.indx = t1.indx;
 
 select * from sys.X_GV$LATCH_MISSES 
where parent_name in ('cache buffers chains') 
  and indx in (2168, 2186, 2218);
  
INDX  INST_ID  PARENT_NAME           WHERE                   NWFAIL_COUNT  SLEEP_COUNT  WTR_SLP_COUNT  LONGHOLD_COUNT  LOCATION
----  -------  --------------------  ----------------------  ------------  -----------  -------------  --------------  ---------------------
2168  1        cache buffers chains  kcbgtcr: fast path exam  0             106323       185902         15798           kcbgtcr: fast path exam
2186  1        cache buffers chains  kcbrls: pin release      0             14959        22770          3187            kcbrls: pin release
2218  1        cache buffers chains  kcbgtcr: slow path excl  0             189591       48044          3576            kcbgtcr: slow path excl
The above output showed that the most LONGHOLD_COUNT is in location "kcbgtcr: fast path exam" when using INDEX UNIQUE SCAN to make TABLE ACCESS. Whereas when using INDEX RANGE SCAN, the top location is "kcbgtcr: fast path pin". This tiny nuance of location change can play a critical role in CBC latch contention investigation. It also shows the different handling of reverse primary key index vs.normal reverse index, which is probably some diligent work of Oracle optimization.

In the above Dtrace, we trace kslgess function. Blog: Oracle 12 and latches, part 2 described it as:
  kslgess: kernel service latch get spin shared (latch)
Blog: Latch, mutex and beyond written:
  kslgess: shared latch wait get
About kcbrls, Blog: Buffer is pinned count said:
  The function kcbrls (Kernel Cache Buffer Release) is used to release a buffer, also it is possible from another places, 
  for example from kcb_post_apply can call kcbzfs (detach handle from x$kcbbf) directly.


5. Buffer Cache Activity Internals


If we make 10200 and 10046 trace for 100 loops for id from 6 to 10, we can see 500 "ktrgtc2 -> ktrexc" INDEX UNIQUE SCAN on the new block (3665609=0x0037eec9).
("ktrexc: for unique index and index root" and "ktrexf: for non-unique index".
see Blog: Oracle Unique Index Non-Consistency Read with RowCR)

alter session set events='10046 trace name context forever, level 12: 
                          10200 trace name context forever, level 10' 
      tracefile_identifier='10046_10200_index_rp_2';  
exec test_tab_select(6, 1000, 6, 0.00);
alter session set events='10046 trace name context off : 10200 trace name context off ';


ktrgtc2(): started for block <0x07cf : 0x0037eec9> objd: 0x00258aa6
ktrexc(): returning 9 
ktrgtc2(): completed for block <0x07cf : 0x0037eec9> objd: 0x00258aa6


6. X$TRACE Latch Operations


In this section, we try to use X$TRACE to trace latch operations (10005). Pick one CBC latch contention session (SID: 367, Oracle PID: 44 (0x2C), spid: 5222) and trace all levels (15).

$ > oerr ora 10005
10005, 00000, "trace latch operations for debugging"
// *Action: Enable tracing for various latch operations
//    level 1 - trace latch gets and frees
//    level 2 - protect/unprotect latch recovery areas
//    level 4 - trace multiple posts by processes when latch is freed
//    level 8 - signal error on bad latch waits


alter tracing enable "10005:15:44";

SQL> select * from x$trace_events where procs in (44);
ADDR              INDX  INST_ID  CON_ID  EVENT  TRCLEVEL  STATUS  PROCS  FLAGS
----------------  ----  -------  ------  -----  --------  ------  -----  -----
FFFF80FFBC1361C8  5     1        0       10005  15        1       44     1
Following query on x$trace shows the latch begin (KSL WAIT BEG) and end (KSL WAIT END) for each seq_num on contention child latch (6636068072/0x18b8a5ce8). One has a longer elapsed time of 169973 usecs, another is short as of 81 usecs.

169973 usecs is about 169 ms. For a CPU time slice of 10 ms, the process can be scheduled to run after 17 sleeps. That is also the case we have observed in some real applications.

select event, seq#, pid, file_loc,function, data 
  from x$trace 
 where pid in (44) 
 order by time;

EVENT SEQ# PID FILE_LOC    FUNCTION  DATA
----- ---- --- ----------- --------- ----------------------------------------------------------------------------------------------------------------------
10005 7186 44  ksl2.c@2966 kslwtbctx KSL WAIT BEG [latch: cache buffers chains] 6636068072/0x18b8a5ce8 228/0xe4 0/0x0 wait_id=11277 seq_num=11278 snap_id=1
10005 7200 44  ksl2.c@3109 kslwtectx KSL WAIT END [latch: cache buffers chains] 6636068072/0x18b8a5ce8 228/0xe4 0/0x0 wait_id=11277 seq_num=11278 snap_id=1
10005 7201 44  ksl2.c@3117 kslwtectx KSL WAIT END wait times (usecs) - snap=169973, exc=169973, tot=169973

10005 7280 44  ksl2.c@2966 kslwtbctx KSL WAIT BEG [latch: cache buffers chains] 6636068072/0x18b8a5ce8 228/0xe4 0/0x0 wait_id=11278 seq_num=11279 snap_id=1
10005 7301 44  ksl2.c@3109 kslwtectx KSL WAIT END [latch: cache buffers chains] 6636068072/0x18b8a5ce8 228/0xe4 0/0x0 wait_id=11278 seq_num=11279 snap_id=1
10005 7302 44  ksl2.c@3117 kslwtectx KSL WAIT END wait times (usecs) - snap=35031, exc=35031, tot=35031

10005 7312 44  ksl2.c@2966 kslwtbctx KSL WAIT BEG [latch: cache buffers chains] 6636068072/0x18b8a5ce8 228/0xe4 0/0x0 wait_id=11279 seq_num=11280 snap_id=1
10005 7317 44  ksl2.c@3109 kslwtectx KSL WAIT END [latch: cache buffers chains] 6636068072/0x18b8a5ce8 228/0xe4 0/0x0 wait_id=11279 seq_num=11280 snap_id=1
10005 7318 44  ksl2.c@3117 kslwtectx KSL WAIT END wait times (usecs) - snap=81, exc=81, tot=81

Thursday, February 20, 2020

Cache Buffer Chains Latch Contention Case Study-1: Reverse Key Index

In Oracle Database, if a reverse key index is heavily accessed by many concurrent sessions, a minor change on the index can perturb whole system with massive cache buffer chains (CBC) latch contention (maybe Butterfly Effect).

In this Blog, we will try to construct such a case of CBC latch contention that one index leaf block split occurs when inserting a single row.

Note: Tested in Oracle 12c, 18c, 19c.


1. Test Setup


First we create a table with one reverse primary key index, and get object_id of that index:

drop table test_tab purge;

create table test_tab (id number, val number);

create index test_tab#r on test_tab(id) reverse;

-- CREATE TABLE test_tab (id NUMBER PRIMARY KEY USING INDEX (CREATE INDEX test_tab#r ON test_tab(id) REVERSE));

select object_id, to_char(object_id, 'XXXXXXXX') id_hex, data_object_id, to_char(data_object_id, 'XXXXXXXX') did_hex 
from dba_objects where object_name = 'TEST_TAB#R';

  OBJECT_ID ID_HEX DATA_OBJECT_ID DID_HEX
  --------- ------ -------------- -------
    2459815 2588A7        2459815  2588A7


2. Leaf Block Split Point Finding


With appended Plsql procedure: leaf_split_finder, we can find one exact point of leaf block split.

SQL > exec leaf_split_finder(5000);

  --- Leaf Split at row count: 4708, (id between 1 and 11) in 2 Blocks
  block: 2661260, cnt: 5, min_id: 1, max_id: 5
  block: 3665613, cnt: 6, min_id: 6, max_id: 11
Above output shows that after inserting 4707 rows into table test_tab, all the index entries for row from 1 to 11 are in the same block (2661260). Now insert of one more row (4708) will cause a leaf block split. The index entries for row from 1 to 5 remain in block (2661260), but index entries for row from 6 to 11 are moved to a new block (3665613).


3. CBC Latch Contention Producing


Open a Plsql Window, truncate table, insert 4707 rows at first.

truncate table test_tab;

insert into test_tab select level, -level from dual connect by level <= 4707;

commit;

-- using above index object_id to substitute for 2458815
select blk, count(*), min(id) min_id, max(id) max_id from (
  select id 
        ,dbms_rowid.rowid_block_number(sys_op_lbid (2459815, 'L', rowid)) blk
    from test_tab)  
where id between 1 and 11
group by blk order by min_id, blk;

      BLK COUNT(*) MIN_ID MAX_ID
  ------- -------- ------ ------
  2661260       11      1     11
We can see all rows from 1 to 11 are in the same leaf block (2661260).

Following query lists all 14 index blocks and number of entries in each of them, something like index entry distribution. The first BLK (2661260) has 534 entries.

select blk, count(*), min(id) min_id, max(id) max_id from (
  select id 
        ,dbms_rowid.rowid_block_number(sys_op_lbid (2461548, 'L', rowid)) blk
    from test_tab)  
group by blk order by min_id, blk;

       BLK  COUNT(*)  MIN_ID  MAX_ID
  -------- --------- ------- -------
   2661260       534       1    4707
   3665593       527      12    4622
   2661262       274      22    4627
   3665612       269      28    4633
   3665595       284      34    4639
   3665611       279      40    4645
   3665614       314      46    4700
   3665598       311      52    4658
   3665599       327      59    4665
   3665596       319      66    4672
   2661261       312      73    4678
   3665594       311      79    4685
   3665592       327      86    4692
   3665597       319      93    4699
  
  14 rows selected.
Now we start two groups of jobs, each group having 6 jobs in a query loop (see appended test_tab_select_job). All 6 jobs in the first group access row 1 to 5; whereas 6 jobs in the second group access row 6 to 10;

exec test_tab_select_job(6, 1e9, 1, 0);

exec test_tab_select_job(6, 1e9, 6, 0);

-- stop all jobs by:
--   exec clean_jobs;
Each job in the first group executes the query with id from 1 to 5 in a loop, whereas job in the second group executes it with id from 6 to 10. Both are with the same Xplan.

select val from test_tab where id = :B1
 
--------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |            |       |       |     1 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TEST_TAB   |     1 |    26 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | TEST_TAB#R |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------- 
A query on v$session shows that all 12 jobs are running without any waiting event. And repeated query on v$latch shows that 'cache buffers chains' SLEEPS is not increasing.

select s.client_identifier, s.sid, s.serial#, p.pid, p.spid, s.event, p1, p2, p3, latchwait, latchspin
  from v$session s, v$process p
 where s.paddr=p.addr and lower(s.program) like '%j0%' 
 order by s.client_identifier; 

  CLIENT_IDENTIFIER     SID  SERIAL#  PID  SPID  EVENT                    P1  P2     P3  LATCHWAIT  LATCHSPIN
  --------------------  ---  -------  ---  ----  -----------------------  --  -----  --  ---------  ---------
  test_tab_select_id_1  900  65141    23   4653  db file sequential read  1   85437  1
  test_tab_select_id_1  7    8997     24   4655  null event               0   0      0
  test_tab_select_id_1  186  50273    25   4657  null event               0   0      0
  test_tab_select_id_1  369  57072    26   4659  null event               0   0      0
  test_tab_select_id_1  547  53721    27   4661  null event               0   0      0
  test_tab_select_id_1  903  13140    35   4663  null event               0   0      0
  test_tab_select_id_6  366  17587    38   4665  null event               0   0      0
  test_tab_select_id_6  9    26667    42   4667  null event               0   0      0
  test_tab_select_id_6  189  13145    43   4669  null event               0   0      0
  test_tab_select_id_6  549  36479    45   4671  null event               0   0      0
  test_tab_select_id_6  726  40173    46   4673  null event               0   0      0
  test_tab_select_id_6  906  16667    47   4675  null event               0   0      0


select addr, gets, misses, sleeps, spin_gets, wait_time, round(wait_time/sleeps) us_per_sleep 
  from v$latch where name = 'cache buffers chains';
  
  ADDR              GETS       MISSES  SLEEPS  SPIN_GETS  WAIT_TIME  US_PER_SLEEP
  ----------------  ---------  ------  ------  ---------  ---------  ------------
  0000000060027E88  181375987  795727  1       795699     25         25
Now insert a single new row:

insert into test_tab values(4708, -4708);

select blk, count(*), min(id) min_id, max(id) max_id from (
  select id 
        ,dbms_rowid.rowid_block_number(sys_op_lbid (2459815, 'L', rowid)) blk
    from test_tab)  
where id between 1 and 11
group by blk order by min_id, blk;

      BLK COUNT(*) MIN_ID MAX_ID
  ------- -------- ------ ------
  2661260        5      1      5
  3665613        6      6     11
One leaf block split occurs. Row 6 to 11 are moved to new block (3665613).

Look again index entry distribution, now it has 15 index blocks. The previous 534 entries in BLK (2661260) are split into two parts, 272 in original block, 262 in new created BLK (3665613), which also contains the new inserted row (id: 4708). So 534 is probably the split point in such case. If we look the third BLK (3665593), which has already 527 index entries, only 7 rows away from next leaf block split. It means that in certain case, the distance of two split points can be as short of 7 rows for 4715 (4708+7) row inserts (average should be 4715/15 = 314), an uneven split point distribution in case of reverse index. In later section, we will give further look of index block split distributions.

select blk, count(*), min(id) min_id, max(id) max_id from (
  select id 
        ,dbms_rowid.rowid_block_number(sys_op_lbid (2461548, 'L', rowid)) blk
    from test_tab)  
group by blk order by min_id, blk;

       BLK  COUNT(*)  MIN_ID  MAX_ID
  -------- --------- ------- -------
   2661260       272       1    4705
   3665613       263       6    4708
   3665593       527      12    4622
   2661262       274      22    4627
   3665612       269      28    4633
   3665595       284      34    4639
   3665611       279      40    4645
   3665614       314      46    4700
   3665598       311      52    4658
   3665599       327      59    4665
   3665596       319      66    4672
   2661261       312      73    4678
   3665594       311      79    4685
   3665592       327      86    4692
   3665597       319      93    4699
  
  15 rows selected.
Now if we look again all 12 job sessions:

select s.client_identifier, s.sid, s.serial#, p.pid, p.spid, s.event, p1, p2, p3, latchwait, latchspin
  from v$session s, v$process p
 where s.paddr=p.addr and lower(s.program) like '%j0%' 
 order by s.client_identifier; 

  CLIENT_IDENTIFIER     SID  SERIAL#  PID  SPID  EVENT                        P1          P2       P3  LATCHWAIT         LATCHSPIN
  --------------------  ---  -------  ---  ----  ---------------------------  ----------  -------  --  ----------------  ---------
  test_tab_select_id_1  900  65141    23   4653  null event                   0           0        0
  test_tab_select_id_1  7    8997     24   4655  null event                   0           0        0
  test_tab_select_id_1  186  50273    25   4657  null event                   0           0        0
  test_tab_select_id_1  369  57072    26   4659  null event                   0           0        0
  test_tab_select_id_1  547  53721    27   4661  latch: cache buffers chains  6630918056  228      0
  test_tab_select_id_1  903  13140    35   4663  latch: cache buffers chains  6630918056  228      0
  test_tab_select_id_6  366  17587    38   4665  latch: cache buffers chains  6632747048  228      0
  test_tab_select_id_6  9    26667    42   4667  buffer busy waits            1548        3665613  1
  test_tab_select_id_6  189  13145    43   4669  latch: cache buffers chains  6632747048  228      0   000000018B57B028
  test_tab_select_id_6  549  36479    45   4671  latch: cache buffers chains  6632747048  228      0   000000018B57B028
  test_tab_select_id_6  726  40173    46   4673  buffer busy waits            1548        3665613  1
  test_tab_select_id_6  906  16667    47   4675  latch: cache buffers chains  6632747048  228      0   000000018B57B028
There are two wait events: "buffer busy waits" (p1text=file#, p2text=block#, p3text=class#) or "latch: cache buffers chains" (p1text=address, p2text=number, p3text=tries. From Oracle 18c, p3text renamed as "why").

For "buffer busy waits", they are waiting on new spli index block (file#=1548, block#=3665613).

For "latch: cache buffers chains", first group are waiting in address 6630918056 (0x18B3BC7A8), whereas second group in 6632747048 (0x18B57B028).

The second group also shows column v$process.LATCHWAIT (000000018B57B028), which is the child latch address the process is waiting for (probably only filled where there exists latch sleep).

Imagine that a production system was running smoothly all morning, Suddenly, a single new row insert slowed it down with overwhelming CBC latch concurrency.

Repeated query on v$latch shows the continously increasing of latch SLEEPS (1460647) and WAIT_TIME (905492637).

select addr, gets, misses, sleeps, spin_gets, wait_time, round(wait_time/sleeps) us_per_sleep 
  from v$latch where name = 'cache buffers chains';

  ADDR              GETS        MISSES    SLEEPS   SPIN_GETS  WAIT_TIME  US_PER_SLEEP
  ----------------  ----------  --------  -------  ---------  ---------  ------------
  0000000060027E88  1853839207  96374121  1460647  95066763   905492637  620
A further query on v$latch_children shows majority of CBC SLEEPS (1460317) is on child latch: 000000018B57B028 of second group. Even though the child latch: 000000018B3BC7A8 of first group has more GETS (428020322), it has much less MISSES, almost no SLEEPS and WAIT_TIME.

select latch#, child#, addr, gets, misses, sleeps, spin_gets, wait_time, round(wait_time/sleeps) us_per_sleep 
  from v$latch_children 
 where addr = hextoraw('000000018B3BC7A8') or addr = hextoraw('000000018B57B028'); 
 
  LATCH#  CHILD#  ADDR              GETS       MISSES    SLEEPS   SPIN_GETS  WAIT_TIME  US_PER_SLEEP
  ------  ------  ----------------  ---------  --------  -------  ---------  ---------  ------------
  228     3156    000000018B3BC7A8  428020322  1027200   4        1027196    108        27
  228     12682   000000018B57B028  380009579  90239103  1460317  88932209   905458824  620
With one more query on v$latch_misses, we can display CBC latch contention locations.

select * from v$latch_misses 
 where parent_name = 'cache buffers chains' and sleep_count > 0
 order by sleep_count desc;

  PARENT_NAME           WHERE                     NWFAIL_COUNT  SLEEP_COUNT  WTR_SLP_COUNT  LONGHOLD_COUNT  LOCATION
  --------------------  ------------------------  ------------  -----------  -------------  --------------  -----------------------
  cache buffers chains  kcbgtcr: fast path pin    0             1341199      2297098        197030          kcbgtcr: fast path pin
  cache buffers chains  kcbchg: cr pin cleanout   0             694146       34319          86549           kcbchg: cr pin cleanout
  cache buffers chains  kcbchg: cr pin change     0             182979       13069          23671           kcbchg: cr pin change
  cache buffers chains  kcbgtcr: slow path excl   0             124511       5269           5873            kcbgtcr: slow path excl
  cache buffers chains  kcbchg: change complete   0             30195        8539           2881            kcbchg: change complete
  cache buffers chains  kcbcge                    0             23214        30005          6855            kcbcge
  cache buffers chains  kcbzwb                    0             15599        9668           2013            kcbzwb
  cache buffers chains  kcbrls: pin release       0             10319        18689          2147            kcbrls: pin release
  cache buffers chains  kcb_trim_hash_chain       0             3527         109            712             kcb_trim_hash_chain
  cache buffers chains  kcbrls: fast pin release  0             1069         10173          503             kcbrls: fast pin release
  cache buffers chains  kcbbxsv                   0             2            0              2               kcbbxsv
  cache buffers chains  kcbgcur: slow path        0             1            0              0               kcbgcur: slow path  
The top Location (SLEEP_COUNT=1341199, LONGHOLD_COUNT=197030) is "kcbgtcr: fast path pin".

With one further query on x$bh, we can link the CBC_LATCH to protected block.

select * from (
with sq as (select object_name, data_object_id 
              from dba_objects where object_name like 'TEST_TAB%')
    ,bh as (select hladdr, obj, file#, dbablk, sum(tch) tch 
              from x$bh group by hladdr, obj, file#, dbablk)
select hladdr cbc_latch_addr
      ,sum(tch) tch
      ,listagg(tch || '-' || obj || '(' || object_name || ')/' || file# || '/' ||dbablk, ';') 
         within group (order by tch desc) tch_list -- "tch-obj(name)/file/blk_list"
      ,count(*) blk_cnt
from  bh, sq
where bh.obj = sq.data_object_id
  and tch > 0
  --and (hladdr like '%18B3DB828%' or hladdr like '%18B3BACA8')   
group by hladdr
order by tch desc)
where 1=1
  and (tch_list like '%TEST_TAB%');
  
  CBC_LATCH_ADDR    TCH  TCH_LIST                             BLK_CNT
  ----------------  ---  -----------------------------------  -------
  000000018B851DA8   3  3-2459816(TEST_TAB#R)/1548/2661258    1       <== root block (kdxcolev 1)
  000000018B3BC7A8 213  213-2459816(TEST_TAB#R)/1548/2661260  1       <== leaf block for row 1 to 5  (kdxcolev 0)
  000000018B57B028   5  5-2459816(TEST_TAB#R)/1548/3665613    1       <== leaf block for row 6 to 11 (kdxcolev 0)
It shows that the contention latch (000000018B57B028) has much lower TCH than that of non contention latch (000000018B3BC7A8). This observation also raises an immediate question: can we use TCH to determine CBC latch contention or Hot Block ?


4. Latch State Transition Diagram By Dtrace


Pick one CBC latch contention session (SID: 549, Oracle PID: 45 (0x2D), spid: 4671) from above v$session and v$process query.

Run following Dtrace Script on contention latch address: 0x18B57B028 (For Latch State Transition Diagram, See Blog: PL/SQL Function Result Cache Invalidation (I) ).

sudo dtrace -n \
'
typedef unsigned int ub4;

typedef struct latchstate /* latch state */ {
    ub4 nproc_pid;  /* Nproc or pid   */
    ub4 flag;       /* blocking flag  */ 
    ub4 gets;       /* number of gets */
    ub4 latch;      /* latch number   */
    ub4 level;      /* latch level    */
    ub4 where; } latchstate;
    
BEGIN {laddr = $1; step = 1; begin_gets = 0, end_gets = 0; prev_ts = timestamp;
   self->evt = "begin"; self->loc = 0x0;}
pid$target::ksl_get_shared_latch:entry /arg0 == laddr && (self->evt != probefunc || self->loc != arg3)/ { 
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   ls = ((latchstate *) copyin(arg0, sizeof(latchstate)));
   begin_gets = (begin_gets == 0 ? ls->gets: begin_gets); end_gets = ls->gets;
   printf("\n***State Entry>>>(nproc_pid=>%d, flag=>%d, gets=>%d, latch=>%d, level=>%d, where=>%d)"
           ,ls->nproc_pid, ls->flag, ls->gets, ls->latch, ls->level, ls->where);
   printf("\n>>>Transition>>>%s:%s\n      (Addr=>0x%X, Immed_Get=>%d, Why=>%d, Where=>%d(0x%X), Mode=>%d, PID=>0x%X)", 
          probefunc, probename, arg0, arg1, arg2, arg3, arg3, arg4, arg5);
   self->evt = probefunc; self->loc = arg3;    
   ustack(10, 0);  
   /* stop(); exit(0); */    /* no latch blocking, failed get, try again */ 
   step = step + 1; self->entry = 1; prev_ts = timestamp;} 
pid$target::ksl_get_shared_latch:return /self->entry == 1/ {
   ls = ((latchstate *) copyin(laddr, sizeof(latchstate))); 
   printf("\n>>>State Return***(nproc_pid=>%d, flag=>%d, gets=>%d, latch=>%d, level=>%d, where=>%d)"
           ,ls->nproc_pid, ls->flag, ls->gets, ls->latch, ls->level, ls->where);
   /* stop(); exit(0); */      /* simulate long latch holder, run by: dtrace -w -n */
   self->entry = 2;}   
pid$target::kslfre:entry /self->entry == 2 && arg0 == laddr && self->evt != probefunc/ {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   ls = ((latchstate *) copyin(arg0, sizeof(latchstate)));
   end_gets = ls->gets;
   printf("\n>>>Transition>>>%s:%s(Addr=>0x%-X)", probefunc, probename, arg0);
   self->evt = probefunc; self->loc = 0x0;
   /* stop(); exit(0); */      /* simulate long latch holder, run by: dtrace -w -n */
   printf("\n===Latch freed===");
   ustack(10, 0); step = step + 1; prev_ts = timestamp;}      
END {
   printf("\n---------Step %d at %Y (Elapsed-ns=%d)---------", step, walltimestamp, (timestamp-prev_ts));
   printf("\nLatch Gets = %d (%d - %d)", (end_gets - begin_gets), (unsigned long)begin_gets, (unsigned long)end_gets); }
' -p 4671 0x18B57B028
The output is some repeated occurrences of following pattern:

   ksl_get_shared_latch:entry
---------Step 599 at 2020 Feb 20 14:10:20 (Elapsed-ns=2112100)---------
***State Entry>>>(nproc_pid=>0, flag=>0, gets=>632751866, latch=>228, level=>1, where=>2195)
>>>Transition>>>ksl_get_shared_latch:entry
      (Addr=>0x18B57B028, Immed_Get=>1, Why=>0, Where=>2298(0x8FA), Mode=>16, PID=>0x200000000000002D)
              oracle`ksl_get_shared_latch
              a.out`kcbzwb+0xa33
              a.out`kcbgtcr+0x14a77
              a.out`ktrget2+0x292
              a.out`qeilbk1+0x3cf
              a.out`qeilsr+0xbd
              a.out`qerixFetch+0x1f9
              a.out`qerstFetch+0x543
              a.out`qertbFetchByRowID+0x39e
              a.out`qerstFetch+0x4ca

   ksl_get_shared_latch:return
>>>State Return***(nproc_pid=>45, flag=>536870912, gets=>632751872, latch=>228, level=>1, where=>2298)
   kslfre:entry
---------Step 600 at 2020 Feb 20 14:10:20 (Elapsed-ns=63625)---------
>>>Transition>>>kslfre:entry(Addr=>0x18B57B028)
===Latch freed===
              oracle`kslfre
              a.out`kcbgtcr+0x540f
              a.out`ktrget2+0x292
              a.out`qeilbk1+0x3cf
              a.out`qeilsr+0xbd
              a.out`qerixFetch+0x1f9
              a.out`qerstFetch+0x543
              a.out`qertbFetchByRowID+0x39e
              a.out`qerstFetch+0x4ca
              a.out`opifch2+0x188b
It shows the CBC Latch State Transition. At first, C subroutine ksl_get_shared_latch (shared Latch (in S Mode or X Mode)) was called to get exclusive latch (Mode=>16) in entry location (where=>2195) and next location (where=>2298). Then freed by kslfre. CallStack shows that each row is accessed by qertbFetchByRowID via qeilsr (TABLE ACCESS BY INDEX via INDEX RANGE SCAN).

For the where location, we can use the query below to find them.

CREATE OR REPLACE FORCE VIEW X_GV$LATCH_MISSES as
SELECT t1.indx              INDX,
       t1.inst_id           INST_ID,       
       t1.ksllasnam         PARENT_NAME,   
       t2.ksllwnam          "WHERE",       
       t1.kslnowtf          NWFAIL_COUNT,  
       t1.kslsleep          SLEEP_COUNT,   
       t1.kslwscwsl         WTR_SLP_COUNT, 
       t1.kslwsclthg        LONGHOLD_COUNT,
       t2.ksllwnam          LOCATION,      
       t1.con_id            CON_ID         
  FROM x$ksllw t2, x$kslwsc t1
 WHERE t2.indx = t1.indx;
 
select * from sys.X_GV$LATCH_MISSES 
where parent_name in ('cache buffers chains') 
  and indx in (2195, 2298, 2162); 
  
INDX  INST_ID  PARENT_NAME           WHERE                   NWFAIL_COUNT  SLEEP_COUNT  WTR_SLP_COUNT  LONGHOLD_COUNT  LOCATION
----  -------  --------------------  ----------------------  ------------  -----------  -------------  --------------  ---------------------
2118  1        cache buffers chains  kcbchg: cr pin cleanout 0             1946366      110291         243655          kcbchg: cr pin cleanout
2195  1        cache buffers chains  kcbgtcr: fast path pin  0             2594671      5314421        307276          kcbgtcr: fast path pin
2298  1        cache buffers chains  kcbzwb                  0             59480        48922          7353            kcbzwb
In the above Dtrace script, if we uncomment the line inside "kslfre:entry":

/* stop(); exit(0); */      /* simulate long latch holder, run by: dtrace -w -n */
the process was stopped (process STATE: stop) and latch will not be freed, hence to simulate long latch holder. (v$session.state: "WAITED SHORT TIME", v$active_session_history.session_state: "ON CPU").

Repeated query on v$latchholder will show GETS is no more changed.

select * from v$latchholder;

PID  SID  LADDR             NAME                  GETS
---  ---  ----------------  --------------------  ----------
45   549  000000018B57B028  cache buffers chains  761549981
Note that if we "stop()" inside "ksl_get_shared_latch:entry", there will be no latch blocking since latch child (000000018B57B028) Get failed and the latch was not acquired. (v$session.state: "WAITING", v$active_session_history.session_state: "WAITING").

However, if we "stop()" inside "ksl_get_shared_latch:return", there will be latch blocking since latch was already acquired, it should be released before any other sessions can get it.

By the way,

  v$active_session_history.session_state 
is defined as

  DECODE (a.wait_time, 0, 'WAITING', 'ON CPU')
That means if "wait_time" is not 0, the session_state is denoted as 'ON CPU'. In this example, UNIX process STATE: "stop" is conveyed to v$active_session_history.session_state: 'ON CPU'. In this case, if v$active_session_history.session_state is aggregated (in an interval) to assess UNIX CPU utilization, the outcome from Oracle will differ from real UNIX CPU utilization.

As of 12c, v$session.WAIT_TIME has been deprecated in favor of the columns WAIT_TIME_MICRO and STATE (Strangely, derived v$active_session_history.WAIT_TIME and SESSION_STATE are not yet deprecated in Oracle 12c Documentation).


5. Buffer Cache Activity Internals


In Oracle, each CBC latch protects a set of hash buckets (32). Each hash bucket is a linked list of buffer headers. Each data (disk) block is cloned as a few buffer blocks with different status (CR, XCUR) into buffer cache (Maximum Allowed Number of CR buffers per dba is controlled by "_db_block_max_cr_dba"). All buffer blocks of same data (disk) block are hashed to the same hash bucket, hence protected by the same CBC latch. If we query the new block (3665613) protected by the CBC contention latch (000000018B57B028) twice, two example outputs are:

select hladdr, decode(state,0,'free',1,'xcur',2,'scur',3,'cr', state) status, 
       decode (bitand (flag, 1),0,'N','Y') dirty, ba, addr, nxt_hash, prv_hash, tch  --, v.* 
from  x$bh v 
where dbablk in (3665613) 
order by v.state, indx;

HLADDR            STATUS  DIRTY  BA                ADDR              NXT_HASH          PRV_HASH          TCH
----------------  ------  -----  ----------------  ----------------  ----------------  ----------------  ---
000000018B57B028  xcur    Y      00000000CA3BA000  FFFF80FFBC132E60  000000018AE0DAC8  00000000CAFD2450  0
000000018B57B028  cr      N      00000000CA8E2000  FFFF80FFBC1341E8  00000000CAFED610  000000018AE0DAC8  0
000000018B57B028  cr      N      00000000CAE24000  FFFF80FFBC134090  00000000CAF9EDD0  00000000CAFB8CD0  1
000000018B57B028  cr      N      00000000CA64A000  FFFF80FFBC133F38  00000000CAF6CA10  00000000CAFED610  1
000000018B57B028  cr      N      00000000CA144000  FFFF80FFBC133DE0  00000000CAF85290  00000000CAF9EDD0  1
000000018B57B028  cr      N      00000000CA3B8000  FFFF80FFBC133110  00000000CAFD2450  00000000CAF6CA10  1
000000018B57B028  cr      N      00000000CAB6E000  FFFF80FFBC132FB8  00000000CAF853D0  00000000CAF85290  1

HLADDR            STATUS  DIRTY  BA                ADDR              NXT_HASH          PRV_HASH          TCH
----------------  ------  -----  ----------------  ----------------  ----------------  ----------------  ---
000000018B57B028  xcur    Y      00000000BE3E6000  FFFF80FFBC18FE08  00000000BEFD76D0  00000000BEF70250  0
000000018B57B028  cr      N      00000000BEE80000  FFFF80FFBC115898  00000000BEFBC650  000000018AE0DAC8  0
000000018B57B028  cr      N      00000000BE93E000  FFFF80FFBC115740  00000000C2F68690  00000000BEFF0F90  1
000000018B57B028  cr      N      00000000C20D8000  FFFF80FFBC1155E8  00000000BEF70610  00000000BEFBC650  1
000000018B57B028  cr      N      00000000BE1A4000  FFFF80FFBC115490  00000000BEFA2D90  00000000C2F68690  1
000000018B57B028  cr      N      00000000BE6B0000  FFFF80FFBC1900B8  00000000BEF70250  00000000BEF70610  1
000000018B57B028  cr      N      00000000BE19E000  FFFF80FFBC18FF60  00000000BEF86F50  00000000BEFA2D90  1
000000018B57B028  cr      N      00000000BEBF2000  FFFF80FFBC18FCB0  00000000BEFBFE90  00000000BEF86F50  0
000000018B57B028  cr      N      00000000BE998000  FFFF80FFBC18FB58  000000018AE0DAC8  00000000BEFD76D0  0
We can see that there exists one single DIRTY buffer block ('Y') with STATUS ('xcur'). The number of rows returned is changing in each execution. All other memory address fields (BA, ADDR, NXT_HASH, PRV_HASH) are different in each execution of the query. That means this buffer chain is constantly changed. For any buffer chain modification, an exclusive latch get is requested. It results in CBC latch contention. However, if we query the original block (2661260) protected by the CBC contention latch (000000018B3BC7A8) a few times, the output are the same and the 'xcur' buffer block is not DIRTY ('N').:

select hladdr, decode(state,0,'free',1,'xcur',2,'scur',3,'cr', state) status, 
       decode (bitand (flag, 1),0,'N','Y') dirty, ba, addr, nxt_hash, prv_hash, tch  --, v.* 
from  x$bh v 
where dbablk in (2661260) 
order by v.state, indx;

HLADDR            STATUS  DIRTY  BA                ADDR              NXT_HASH          PRV_HASH          TCH
----------------  ------  -----  ----------------  ----------------  ----------------  ----------------  ---
000000018B3BC7A8  xcur    N      00000000CA14E000  FFFF80FFBC133110  00000000CAF85A10  00000000CAF9EF10  45
000000018B3BC7A8  cr      N      00000000CAB76000  FFFF80FFBC133F38  00000000CAF9EF10  000000018A7115B8  1
000000018B3BC7A8  cr      N      00000000CA64C000  FFFF80FFBC133DE0  00000000CAF6D050  00000000CAFD2950  1
000000018B3BC7A8  cr      N      00000000CA3C4000  FFFF80FFBC132FB8  00000000CAFB8E10  00000000CAF6D050  1
000000018B3BC7A8  cr      N      00000000CA8E4000  FFFF80FFBC132E60  000000018A7115B8  00000000CAF85A10  1
If we dump the contention buffer block (3665613), the buffer head section looks like:

-- alter system dump datafile 1548 block 3665613; 

BH (0xcafd2618) file#: 1548 rdba: 0x0037eecd (1024/3665613) class: 1 ba: 0xcab72000
  hash: [0xcaf9ec90,0x18ae0dac8] lru: [0xcafd2200,0x1897cc750]
  st: CR md: NULL tch: 1
  flags: only_sequential_access
BH (0xcaf9ebd8) file#: 1548 rdba: 0x0037eecd (1024/3665613) class: 1 ba: 0xca648000
  hash: [0xcaf6c8d0,0xcafd26d0] lru: [0xcaf9ea40,0x1897cb860]
  st: CR md: NULL tch: 1
  flags: only_sequential_access
BH (0xcaf6c818) file#: 1548 rdba: 0x0037eecd (1024/3665613) class: 1 ba: 0xca142000
  hash: [0xcafb8f50,0xcaf9ec90] lru: [0xcaf6ccc0,0x1897cd640]
  st: CR md: NULL tch: 1
  flags: only_sequential_access
BH (0xcafb8e98) file#: 1548 rdba: 0x0037eecd (1024/3665613) class: 1 ba: 0xca8e6000
  hash: [0xcaf84ed0,0xcaf6c8d0] lru: [0xcafb8800,0x1897cbfd8]
  st: CR md: NULL tch: 1
  flags: only_sequential_access
BH (0xcaf84e18) file#: 1548 rdba: 0x0037eecd (1024/3665613) class: 1 ba: 0xca3b2000
  hash: [0xcaf853d0,0xcafb8f50] lru: [0xcaf85400,0x1897cb0e8]
  st: CR md: NULL tch: 1
  flags: only_sequential_access
BH (0xcaf85318) file#: 1548 rdba: 0x0037eecd (1024/3665613) class: 1 ba: 0xca3ba000
  set: 13 pool: 3 bsz: 8192 bsi: 0 sflg: 2 pwc: 14,19
  dbwrid: 0 obj: 2459815 objn: 2459815 tsn: [0/1999] afn: 1548 hint: f
  hash: [0x18ae0dac8,0xcaf84ed0] lru: [0xcaf85180,0xcaf84f00]
  obj-flags: object_ckpt_list
  ckptq: [0x18be25400,0xfffab158] fileq: [0x18be31580,0x18be31580]
  objq: [0x1750dac40,0x1750dac40] objaq: [0xcaf8af78,0x1750dac20]
  use: [0x18bab8240,0x18bab8240] wait: [0x189465eb8,0x18bab9860]
  st: XCURRENT md: EXCL fpin: 'ktspbwh2: ktspfmdb' fscn: 0x896.f43f474c tch: 0
  flags: buffer_dirty mod_started block_written_once
  change state: NOT_ACTIVE_YET
  change count: 1
  LRBA: [0x1a9a.d1c5d.0] LSCN: [0x896.f4ef66c5] HSCN: [0x896.f50b9757] HSUB: [1]
There are 6 buffer blocks, one is with "st: XCURRENT md: EXCL", all other with "st: CR md: NULL". If we pick all the hash fields, we can build a linked list: (see Blog: [Oracle] Database cache internals and why one data block can use multiple cache buffers)

                          NXT_HASH   PRV_HASH         Link_List
                         ----------- -----------      ---------
1. BH (0xcafd2618) hash: [0xcaf9ec90,0x18ae0dac8]     1    -> 3
2. BH (0xcaf9ebd8) hash: [0xcaf6c8d0, 0xcafd26d0]     head -> 4  
3. BH (0xcaf6c818) hash: [0xcafb8f50, 0xcaf9ec90]     3    -> 5
4. BH (0xcafb8e98) hash: [0xcaf84ed0, 0xcaf6c8d0]     4    -> 6
5. BH (0xcaf84e18) hash: [0xcaf853d0, 0xcafb8f50]     5    -> tail
6. BH (0xcaf85318) hash: [0x18ae0dac8,0xcaf84ed0]     6    -> 1  
If we look the index row section of above buffer dump, we can see the new inserted row (id: 4708) are in the same block as split id from 6 to 10 (all ID are reversed), and row (id: 4708) is locked ("lock: 2") by Itl (0x02).

 seg/obj: 0x2588a7  csc: 0x896.f4ef5c84  itc: 2  flg: E  typ: 2 - INDEX
 
 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0002.004.00dbbdfb  0x00c022d5.b648.0a  C---    0  scn 0x0896.f43f4854
0x02   0x0004.004.00769215  0x00c021d1.39b9.27  ----    1  fsc 0x0000.00000000
Leaf block dump
===============
kdxcolev 0
kdxconro 263
...
row#20[4884] flag: -------, lock: 0, len=12
col 0; len 2; (2):  07 c1                    REVERSE=> c1 07    => 6
col 1; len 6; (6):  00 28 2b 85 00 05
row#69[5519] flag: -------, lock: 0, len=12
col 0; len 2; (2):  08 c1                    REVERSE=> c1 08    => 7
col 1; len 6; (6):  00 28 2b 85 00 06
row#117[4611] flag: -------, lock: 2, len=13
col 0; len 3; (3):  09 30 c2                 REVERSE=> c2 30 09 => 4708
col 1; len 6; (6):  00 37 ee c2 00 16
row#118[6154] flag: -------, lock: 0, len=12
col 0; len 2; (2):  09 c1                    REVERSE=> c1 09    => 8
col 1; len 6; (6):  00 28 2b 85 00 07
row#166[6776] flag: -------, lock: 0, len=12
col 0; len 2; (2):  0a c1                    REVERSE=> c1 0a    => 9
col 1; len 6; (6):  00 28 2b 85 00 08
row#214[7398] flag: -------, lock: 0, len=12
col 0; len 2; (2):  0b c1                    REVERSE=> c1 0b    => 10
col 1; len 6; (6):  00 28 2b 85 00 09
If we make 10200 and 10046 trace for 100 loops for id from 6 to 10, we can see 500 "ktrgcm()" calls, and a few long 'latch: cache buffers chains' WAIT (see Blog: Oracle Unique Index Non-Consistency Read with RowCR). 'xcur' (state 1) buffer block is finally returned after 5 ktrexf() showing buffer of 'cr' (state 3).

alter session set events='10046 trace name context forever, level 12: 
                          10200 trace name context forever, level 10' 
      tracefile_identifier='10046_10200_index_r_2';  
exec test_tab_select(6, 100, 6, 0.00);
alter session set events='10046 trace name context off : 10200 trace name context off ';

ktrget2(): started for block  <0x07cf : 0x0037eecd> objd: 0x002588a7     
showing buffer 0x9ffeda58 rdba 0x37eecd state 3
ktrexf(): returning 9 on:  9ffedad0  cr-scn: 0x0897.0ed85359   xid: 0x0000.000.00000000
showing buffer 0x9ffd3a18 rdba 0x37eecd state 3
ktrexf(): returning 9 on:  9ffd3a90  cr-scn: 0x0897.0ed85358   xid: 0x0004.004.00769215
showing buffer 0x9ffb5298 rdba 0x37eecd state 3
ktrexf(): returning 1 on:  9ffb5310  cr-scn: 0x0897.0ed85358   xid: 0x0004.004.00769215
showing buffer 0x9ffed558 rdba 0x37eecd state 3
ktrexf(): returning 9 on:  9ffed5d0  cr-scn: 0x0897.0ed85357   xid: 0x0004.004.00769215
showing buffer 0x9ff9bb18 rdba 0x37eecd state 3
ktrexf(): returning 1 on:  9ff9bb90  cr-scn: 0x0897.0ed85356   xid: 0x0004.004.00769215
showing buffer 0x9ff94f98 rdba 0x37eecd state 1
ktrexf(): returning 1 on:  ffff80ffbccf4e70  cr-scn: 0xffff.ffffffff   xid: 0x0000.000.00000000
ktrgcm(): completed for block  <0x07cf : 0x0037eecd> objd: 0x002588a7
ktrget2(): completed for  block <0x07cf : 0x0037eecd> objd: 0x002588a7
......

WAIT #18446604434568798512: nam='latch: cache buffers chains' ela= 151231 address=6635150120 number=228 tries=0

-- Legend
--   DATA_OBJECT_ID: 2459815 = 0x002588a7
--   new index block 3665613 = 0x0037eecd
--   tablespace ts#     1999 = 0x07cf
--   state 1 = 'xcur'
--   state 3 = 'cr'
Above 10200 trace shows that we have to traverse 5 'cr' (state 3) buffer blocks in the CBC chain before reaching 'xcur' (state 1) buffer block, and construct the required before-image buffer block. Then require CBC latch in exclusive mode to link the new buffer block into the CBC chain.

A query on v$sesstat reveals heavy CR buffer clones (by applying undo records) in the sessions of second group (for id from 6 to 10), but not the case of first group (for id from 1 to 5).

select s.client_identifier, st.sid, n.name, st.value, 
       value-lag(value) over(partition by s.sid order by n.name) delta 
from v$sesstat st, v$statname n, v$session s
where st.statistic# = n.statistic#
  and n.name in ('CR blocks created', 'data blocks consistent reads - undo records applied')
  and st.sid = s.sid
  and lower(s.program) like '%j0%'
order by s.client_identifier, s.sid, n.name;

CLIENT_IDENTIFIER     SID  NAME                                                 VALUE     DELTA
--------------------  ---  ---------------------------------------------------  --------  ------
test_tab_select_id_1  900  CR blocks created                                    1
test_tab_select_id_1  900  data blocks consistent reads - undo records applied  0         -1
test_tab_select_id_1  7    CR blocks created                                    4
test_tab_select_id_1  7    data blocks consistent reads - undo records applied  3         -1
test_tab_select_id_1  186  CR blocks created                                    1
test_tab_select_id_1  186  data blocks consistent reads - undo records applied  2         1
test_tab_select_id_1  369  CR blocks created                                    1
test_tab_select_id_1  369  data blocks consistent reads - undo records applied  1         0
test_tab_select_id_1  547  CR blocks created                                    0
test_tab_select_id_1  547  data blocks consistent reads - undo records applied  0         0
test_tab_select_id_1  903  CR blocks created                                    0
test_tab_select_id_1  903  data blocks consistent reads - undo records applied  0         0

CLIENT_IDENTIFIER     SID  NAME                                                 VALUE     DELTA
--------------------  ---  ---------------------------------------------------  --------  ------
test_tab_select_id_6  366  CR blocks created                                    62217832
test_tab_select_id_6  366  data blocks consistent reads - undo records applied  62134369  -83463
test_tab_select_id_6  9    CR blocks created                                    62673868
test_tab_select_id_6  9    data blocks consistent reads - undo records applied  62663191  -10677
test_tab_select_id_6  189  CR blocks created                                    62222626
test_tab_select_id_6  189  data blocks consistent reads - undo records applied  62255779  33153
test_tab_select_id_6  549  CR blocks created                                    62098359
test_tab_select_id_6  549  data blocks consistent reads - undo records applied  62139622  41263
test_tab_select_id_6  726  CR blocks created                                    62263906
test_tab_select_id_6  726  data blocks consistent reads - undo records applied  62293706  29800
test_tab_select_id_6  906  CR blocks created                                    62227364
test_tab_select_id_6  906  data blocks consistent reads - undo records applied  62217302  -10062
Finally, if we commit the single row insert, CBC latch contention disappears immediately.

For more discussion on reverse key index, see:
Blog: reverse key index: the remedy is worse than the symptoms
and right linked book: Oracle Database Performance Tuning (Studies . Practices . Researches) - Chapter 3: Locks, Latches and Mutexes


6. Data Access Path (Xplan) and Latch Locations


In the above test, we use INDEX RANGE SCAN to access table row:

select /*+ job 4, id 6 */ val from test_tab where id = 7
 
--------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |            |       |       |     1 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TEST_TAB   |     1 |    26 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | TEST_TAB#R |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------- 
If we only use INDEX RANGE SCAN without accessing table row:
(In procedure test_tab_select, use second query statement)

select /*+ job 4, id 6 */ id from test_tab where id = 7
 
-------------------------------------------------------------------------------
| Id  | Operation        | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |            |       |       |     1 (100)|          |
|*  1 |  INDEX RANGE SCAN| TEST_TAB#R |     1 |    13 |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------
The top Location (most SLEEP_COUNT) is changed to "kcbchg: cr pin cleanout" (INDX=2118) instead of "kcbgtcr: fast path pin" (INDX=2195). Here the Dtrace State Transition Diagram (CallStack has no more "qertbFetchByRowID"):

   ksl_get_shared_latch:entry
---------Step 2621 at 2020 Feb 20 15:53:35 (Elapsed-ns=5633)---------
***State Entry>>>(nproc_pid=>0, flag=>0, gets=>1414234634, latch=>228, level=>1, where=>2118)
>>>Transition>>>ksl_get_shared_latch:entry
      (Addr=>0x18B57B028, Immed_Get=>1, Why=>0, Where=>2298(0x8FA), Mode=>16, PID=>0x2000000000000019)
              oracle`ksl_get_shared_latch
              a.out`kcbzwb+0xa33
              a.out`kcbgtcr+0x14a77
              a.out`ktrget2+0x292
              a.out`qeilbk1+0x3cf
              a.out`qeilsr+0xbd
              a.out`qerixFetch+0x1f9
              a.out`qerstFetch+0x4ca
              a.out`opifch2+0x188b
              a.out`opiefn0+0x1f7

   ksl_get_shared_latch:return
>>>State Return***(nproc_pid=>25, flag=>536870912, gets=>1414234638, latch=>228, level=>1, where=>2298)


7. Test Code



7.1. leaf_split_finder



create or replace procedure leaf_split_finder(p_find_cnt number) as
  type t_blk_tab is table of varchar2(100) index by pls_integer;
  l_blk_tab         t_blk_tab;
  l_object_id       number;
  l_stmt            varchar2(32767);
begin
 select object_id into l_object_id from dba_objects where object_name = 'TEST_TAB#R';
  execute immediate 'truncate table test_tab';
  
  l_stmt := q'[
      select 'block: '||blk||', cnt: '||cnt||', min_id: '||min_id||', max_id: '||max_id 
      from (
        select blk, count(*) cnt, min(id) min_id, max(id) max_id from (
         select id, dbms_rowid.rowid_block_number(sys_op_lbid (]'||l_object_id||q'[, 'L', rowid)) blk   
           from test_tab)  
       where id between 1 and 11  -- Note using "id in (1, 6)", Toad crashed due to "sys_op_lbid"
       group by blk order by min_id, blk)               
      ]';
  
  for i in 1..p_find_cnt loop
    insert into test_tab values(i, -i);
   execute immediate l_stmt bulk collect into l_blk_tab;
   if l_blk_tab.count >= 2 then
     dbms_output.put_line('--- Leaf Split at row count: '||i||
                          ', (id between 1 and 11) in '||l_blk_tab.count||' Blocks');
     for i in 1..l_blk_tab.count loop
      dbms_output.put_line(l_blk_tab(i));
    end loop;
    exit;
  end if; 
  end loop;
  rollback;  
end;
/

  -- exec leaf_split_finder(5000);
  -- 
  --   --- Leaf Spli at row count: 4707, (id between 1 and 11) in 2 Blocks
  --   block: 3665598, cnt: 5, min_id: 1, max_id: 5
  --   block: 3665619, cnt: 6, min_id: 6, max_id: 11


7.2. test_tab_select_job



create or replace procedure test_tab_select (p_job_id number, p_cnt number, p_id number, p_sleep number) as
  l_stmt varchar2(1000);
  l_id   number;
  l_ret  number;  
begin
 dbms_session.set_identifier('test_tab_select_id_'||p_id);
  for i in 1..p_cnt loop
    for r in 1..5 loop
      l_id := p_id + r - 1;
      l_stmt := 'select /*+ job '||p_job_id||', id '||p_id||' */ val from test_tab where id = '||l_id;
      -- select id from test_tab, only using reverse index test_tab#r, no table access 
      --l_stmt := 'select /*+ job '||p_job_id||', id '||p_id||' */ id from test_tab where id = '||l_id;
      -- dynamic statement makes each session has a different statement and avoid cusor pin S
      execute immediate l_stmt into l_ret;  
    end loop;
    if p_sleep > 0 then 
      dbms_lock.sleep(p_sleep);
    end if;
  end loop;
end;
/

  -- exec test_tab_select(1, 100, 1, 0.00);
  -- exec test_tab_select(6, 100, 6, 0.00);

create or replace procedure test_tab_select_job(p_job_cnt number, p_cnt number, p_id number, p_sleep number) as
  l_job_id     pls_integer;
begin
  for i in 1.. p_job_cnt loop
   dbms_job.submit(l_job_id, 
    'begin test_tab_select('||i||','||p_cnt||','||p_id||','||p_sleep||');  end;');
  end loop;
  commit;
end;    
/

  -- exec clean_jobs;
  -- exec test_tab_select_job(6, 1e9, 1, 0);
  -- exec test_tab_select_job(6, 1e9, 6, 0); 
  -- select * from dba_jobs;                  
  
create or replace procedure clean_jobs as
begin
  for c in (select job from dba_jobs) loop
    begin
       dbms_job.remove (c.job);
    exception when others then null;
    end;
    commit;
  end loop;

  for c in (select d.job, d.sid, (select serial# from v$session where sid = d.sid) ser 
              from dba_jobs_running d) loop
    begin
      execute immediate
             'alter system kill session '''|| c.sid|| ',' || c.ser|| ''' immediate';
      dbms_job.remove (c.job);
    exception when others then null;
    end;
    commit;
  end loop;
  
  -- select * from dba_jobs;
  -- select * from dba_jobs_running;
end;
/

  -- exec clean_jobs; -- stop all jobs