Saturday, July 28, 2018

row cache mutex in Oracle 12.2.0.1.0

In Oracle 12.2.0.1.0 (12cR2), "row cache mutex" replaced 12.1.0.2.0 (12cR1) "latch: row cache objects", similar to "latch: library cache" substitution by "library cache: mutex X" in the previous release.

Oracle Global Customer Support Document: Oracle Database 12c Release 2 Support Lessons Learned from Early Adopters wrote:
    "row cache mutex" replaces "latch: row cache objects" but rarely seen

As a start point to be acquainted with this new mechanism, this Blog takes the same Test Code in Blog: nls_database_parameters, dc_props, latch: row cache objects to focus only on Dictionary Cache dc_props.

We will run tests in both 12cR2 and 12cR1 by varying the number of parallel sessions, compare their behaviors, and then try to measure their scalability.

Note: All tests are done in 12.2.0.1.0 (12cR2) and 12.1.0.2.0 (12cR1) on Linux with 6 physical processors.

Update: (31-Aug-2021): "row cache mutex" seems caused hot objects, see:

  Bug 31135517 - High row cache mutex waits on hot objects - superseded (Doc ID 31135517.8)
  
  Description
      This problem is introduced in release 12.2 when latches are replaced with the introduction
      of row cache mutexes.
       
      In nearly all cases, the mutex architecture is better because the locking can be targeted at
      the object level. Because the mutexes are embedded within the objects themselves, there are
      no false conflicts caused by sessions trying to lock different objects within the same cache.
      So in general, contention is improved.
       
      It is unfortunate that where  one or two very 'hot' row cache objects exist(s), the previous
      architecture is better because the current architecture requires two mutex compared to one
      latch get.  The reasons for this are complex, but it is primarily because many row cache objects
      have multiple keys, and are linked into the hash table on multiple hash chains. The potential for
      contention is increased because the two mutex gets introduce multiple choke points.
       
      The fix implements 'hot object cloning' to alleviate row cache mutex  contention.  
       
      Stack from hang analyze trace will likely contain:
      ... kslwaitctx ksfwaitctx kgxWait kgxExclusive kqrGetMutexByAddr ..


1. 12cR2 "row cache mutex" vs. 12cR1 "latch: row cache objects"


The new Event "row cache mutex" is exposed with row cache parameters:

  P1TEXT = cache id 
  P2TEXT = where requested

select event, p1text, p1, p2text, p2, p3text, p3
  from v$session v where event in ('row cache mutex');
  
EVENT            P1TEXT    P1  P2TEXT           P2  P3TEXT  P3
---------------  --------  --  ---------------  --  ------  --
row cache mutex  cache id  15  where requested  19          0
"cache id" can be used to directly pinpoint the exact contention row cache objects.

Whereas in lower Releases, Event "latch: row cache objects" is exposed with latch parameters, and v$latch_children.child# is used to join with x$kqrst.kqrstcln and filtered by v$latch_children.addr = :P1RAW to find row cache info.

select event, p1text, p1, p1raw, p2text, p2, p2raw, p3text, p3, p3raw
  from v$session v where event in ('latch: row cache objects');

EVENT                     P1TEXT   P1          P1RAW             P2TEXT  P2   P2RAW             P3TEXT  P3  P3RAW
------------------------  -------  ----------  ----------------  ------  ---  ----------------  ------  --  -----
latch: row cache objects  address  5970309816  0000000163DBB2B8  number  411  000000000000019B  tries   0   00

select la.child# latch_child, kqrstcid cache#, kqrsttxt parameter, la.addr latch_addr --, dc.*, la.*
from x$kqrst dc, v$latch_children la 
where dc.kqrstcln = la.child# 
  and la.name     = 'row cache objects'
  and la.addr     = '0000000182D98958'; 

LATCH_CHILD  CACHE#  PARAMETER  LATCH_ADDR
-----------  ------  ---------  ----------------
18           15      dc_props   0000000163DBB2B8
12cR2 v$rowcache contains 71 rows, one new dictionary cache: 'dc_cdbprops' is added.

By the way, 'row cache objects' LATCH# is numbered as "506" (12cR1 is "411"), but hardly used.

select name, latch# from v$latch where name in ('row cache objects');

NAME               LATCH#  GETS
-----------------  ------  ----
row cache objects  506     456


2. Test


Run following test script in 12cR2 and 12cR1 on a Linux with 6 physical processors.
The script consists of 8 cases with 1, 2, 3, 4, 5, 6, 9 and 12 parallel sessions respectively.
Each case runs for 10 minutes.

declare
  type    t_num_tab is table of number;
  l_nums  t_num_tab := new t_num_tab(1, 2, 3, 4, 5, 6, 9, 12);
begin
  clean_jobs; 
  sys.dbms_workload_repository.create_snapshot('ALL');
  
  for n in 1..l_nums.count loop
    nls_select_jobs(l_nums(n), 1e9);    -- n Sessions
    dbms_lock.sleep(600);               -- 10 minutes
    clean_jobs; 
    sys.dbms_workload_repository.create_snapshot('ALL');
  end loop;
end;
/  


3. Test Result


After test, collect all 8 AWR reports in 12cR2 and 12cR1, extract relevant 4 Sections to compare.


3.1 12cR2


Top 10 Foreground Events by Total Wait Time

Session# Event            Waits     Total Wait Time (sec)   Avg Wait   % DB time  Wait Class
-------- ---------------  --------  ---------------------  --------   ---------   -----------
1        row cache mutex  9         0                       10.33us    0.0        Concurrency
2        row cache mutex  12,836    18                      1.46ms     1.6        Concurrency
3        row cache mutex  48,390    111                     2.31ms     5.9        Concurrency
4        row cache mutex  111,312   280                     2.52ms     10.5       Concurrency
5        row cache mutex  199,253   491                     2.47ms     14.6       Concurrency
6        row cache mutex  322,542   886                     2.75ms     20.1       Concurrency
9        row cache mutex  418,819   3030                    7.24ms     35.6       Concurrency
12       row cache mutex  573,556   4673                    8.15ms     38.7       Concurrency

SQL ordered by Elapsed Time
(SQL Id:   7mgsfc44trnr8
 SQL Text: SELECT VALUE FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER = 'NLS_CHARACTERSET')

Session# Elapsed Time (s) Executions  Elapsed Time per Exec (s)  %Total  %CPU     %IO 
-------- ---------------- ----------  -------------------------  ------  -------  ----
1        498              3,174,194   0.00                       81.47   100.40   0.00
2        983              5,876,575   0.00                       83.48   98.52    0.00
3        1,515            8,383,434   0.00                       79.99   93.22    0.00
4        2,087            10,518,830  0.00                       77.82   87.48    0.00
5        2,533            11,688,284  0.00                       74.97   81.51    0.00
6        3,112            12,242,878  0.00                       70.67   71.93    0.00
9        4,894            9,121,274   0.00                       57.49   34.12    0.00
12       6,685            9,300,453   0.00                       55.32   25.77    0.00

Mutex Sleep Summary 

Session# Mutex Type  Location     Sleeps     Wait Time (ms)
-------- ----------  -----------  ---------  --------------
1        Row Cache   [19] kqrpre  12         0   
2        Row Cache   [19] kqrpre  16,734     18,744  
3        Row Cache   [19] kqrpre  70,398     108,322
4        Row Cache   [19] kqrpre  164,963    266,506
5        Row Cache   [19] kqrpre  290,989    466,843
6        Row Cache   [19] kqrpre  478,910    831,835
9        Row Cache   [19] kqrpre  890,437    2,929,696   
16       Row Cache   [19] kqrpre  1,268,073  4,499,378

1        Row Cache   [13] kqreqd  8          0 
2        Row Cache   [13] kqreqd  415        75
3        Row Cache   [13] kqreqd  1,536      300
4        Row Cache   [13] kqreqd  4,555      850
5        Row Cache   [13] kqreqd  9,913      1,625
6        Row Cache   [13] kqreqd  27,487     3,565
9        Row Cache   [13] kqreqd  20,223     14,429
16       Row Cache   [13] kqreqd  24,497     22,772

(Row Cache  [19] kqrpre: find obj 
 Row Cache  [13] kqreqd: reget)

Dictionary Cache Stats

Session# Cache     Get Requests  Pc t Miss  Scan Reqs  Pct Miss  Mod Reqs  Final Usage
-------- --------  ------------  ---------  ---------  --------  --------  -----------
1        dc_props  222,194,045   0.00       0                    0         70
2        dc_props  406,987,135   0.00       0                    0         70
3        dc_props  572,806,160   0.00       0                    0         70
4        dc_props  708,102,414   0.00       0                    0         70
5        dc_props  776,478,519   0.00       0                    0         70
6        dc_props  807,080,147   0.00       0                    0         70
9        dc_props  605,040,134   0.00       0                    0         70
12       dc_props  616,520,863   0.00       0                    0         70


3.2 12cR1


Top 10 Foreground Events by Total Wait Time

Session# Event                      Waits      Total Wait Time (sec)  Avg Wait(ms)   % DB time   Wait Class
-------- ------------------------   ---------  ---------------------  ------------   ---------   -----------
1        latch: row cache objects   12         0                      0.02           0.0         Concurrency
2        latch: row cache objects   19,775     1                      0.03           0.1         Concurrency
3        latch: row cache objects   259,069    6                      0.02           0.3         Concurrency
4        latch: row cache objects   612,329    16                     0.03           0.7         Concurrency
5        latch: row cache objects   1,008,481  36                     0.04           1.2         Concurrency
6        latch: row cache objects   1,189,823  116                    0.10           3.1         Concurrency
9        latch: row cache objects   1,036,139  2441                   2.36           30.2        Concurrency
12       latch: row cache objects   948,170    3861                   4.07           34.5        Concurrency

SQL ordered by Elapsed Time

Session# Elapsed Time (s)  Executions  Elapsed Time per Exec (s)  %Total  %CPU    %IO 
-------- ----------------  ----------  -------------------------  ------  ------  ----
1        506               6,164,161   0.00                       85.76   100.53  0.00
2        1,056             10,848,660  0.00                       88.01   100.32  0.00
3        1,614             9,977,224   0.00                       91.07   99.91   0.00
4        2,227             8,172,259   0.00                       93.72   99.69   0.00
5        2,843             6,951,871   0.00                       94.78   99.06   0.00
6        3,493             6,890,154   0.00                       93.93   96.45   0.00
9        5,243             7,245,168   0.00                       64.80   49.04   0.00
12       6,728             7,562,209   0.00                       60.19   36.65   0.00

Latch Sleep Breakdown

Session# Latch Name         Get Requests   Misses       Sleeps     Spin Gets
-------- -----------------  -------------  -----------  ---------  -----------
1        row cache objects  1,109,597,151  633          14         619   
2        row cache objects  1,953,334,110  105,279,309  20,757     105,259,556
3        row cache objects  1,796,543,339  378,078,181  269,547    377,819,261
4        row cache objects  1,471,777,609  541,981,017  632,528    541,369,153
5        row cache objects -3,043,081,012  596,060,413  1,034,539  595,053,127
6        row cache objects  1,229,364,378  689,496,609  1,200,975  688,326,423
9        row cache objects  1,304,630,648  561,205,207  1,091,371  560,171,774
16       row cache objects  1,361,872,758  607,120,518  1,007,494  606,174,258

 * -3,043,081,012 is due to 32-bit overflow, it should be 1,251,886,284 (=2^32 - 3,043,081,012).

Dictionary Cache Stats

Session# Cache     Get Requests  Pc t Miss  Scan Reqs  Pct Miss  Mod Reqs  Final Usage
-------- --------  ------------  ---------  ---------  --------  --------  -----------
1        dc_props  369,850,105   0.00       0                    0         60
2        dc_props  651,060,932   0.00       0                    0         60
3        dc_props  598,832,676   0.00       0                    0         60
4        dc_props  490,574,970   0.00       0                    0         60
5        dc_props  417,291,212   0.00       0                    0         60
6        dc_props  413,622,949   0.00       0                    0         60
9        dc_props  434,872,658   0.00       0                    0         60
12       dc_props  453,953,683   0.00       0                    0         60


4. Discussions


4.1 Top 10 Foreground Events by Total Wait Time


    12cR2 "row cache mutex" has about 2 times less "Waits" than 12cR1 "latch: row cache objects".
    That is the advantage of light-weight "Mutex" over "Latch".
    "Total Wait Time" and "Avg Wait" increase linearly when Session# <= 6 (number of physical processors), non-linearly when > 6.
    12cR2 "row cache mutex" has higher concurrency ("% DB time") than 12cR1 "latch: row cache objects".

    Strangely 12cR2 Mutex "Avg Wait" is much higher than that of 12cR1 Latch.


4.2 SQL ordered by Elapsed Time


    "Executions" in 12cR2 reaches maximum at Session# = 6, 20% higher than 12cR1 peak value at Session# = 2.
    It means 12cR1 can use less CPU resource to reach its peak value.

    12cR1 has higher throughput than 12cR2 when less Sessions (less than 4); but lower when more Sessions.


4.3 Mutex Sleep Summary vs Latch Sleep Breakdown


    12cR2 "row cache mutex" Sleeps and Wait Time increase non-linearly.
    12cR1 "latch: row cache objects" Misses and Sleeps increase non-linearly although "Get Requests" are almost similar.

    In 12cR1, "Waits" in AWR "Top 10 Foreground Events by Total Wait Time" is close to "Sleeps" in "Latch Sleep Breakdown".
    in 12cR2, it is far from "Sleeps" in "Mutex Sleep Summary".


4.4 Dictionary Cache Stats


    dc_props "Get Requests" depends on query "Executions".
    Each query (SQL Id: 7mgsfc44trnr8) Execution triggers 70 "Get Requests" in 12cR2, and 60 in 12cR1
    since dc_props rows (Field "Final Usage") increased from 60 in 2cR1 to 70 in 12cR2.


5. Test Code


create or replace procedure nls_select(p_cnt number) as
  l_val VARCHAR2 (256 Byte);
begin
for i in 1..p_cnt loop
  select value into l_val from nls_database_parameters where parameter = 'NLS_CHARACTERSET';
end loop;
end;
/

create or replace procedure nls_select_jobs(p_job_cnt number, p_cnt number) as
  l_job_id pls_integer;
begin
  for i in 1.. p_job_cnt loop
    dbms_job.submit(l_job_id, 'begin while true loop nls_select('||p_cnt||'); end loop; end;');
  end loop;
  commit;
end;    
/

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;
  
  dbms_lock.sleep(2);
  
  -- select * from dba_jobs;
  -- select * from dba_jobs_running;
end;
/

-- exec nls_select_jobs(2, 1e9);
-- exec clean_jobs; -- stop all jobs

plshprof command-line utility in Oracle 12.2.0.1.0

The plshprof command-line utility generates HTML reports from raw profiler output files.

In Oracle 12.2.0.1.0 (12cR2), plshprof is changed in three aspects:
    (1). only one single HTML file is generated. However, in 12.1.0.2.0 and lower versions, 16 HTML files are generated.
    (2). new Section: SQL ID Elapsed Time (microsecs) Data sorted by SQL ID.
    (3). throws Memory fault when there exist new line characters in raw profiler output files.


1. 16 HTML files assemble in 12.1.0.2.0


In 12.1.0.2.0, plshprof generates 16 HTML files. To faciliate deploying and displaying, they can be congregated into one single file by following shell script (publicly presented in Oracle Open World 2015).


#!/bin/ksh
# USAGE: plshprof.ksh <directory-of-raw-profile> <profile-name>
# Basic script without input checking / exception handling. Use at your own risk. Environment variable ORACLE_BIN must be defined and user must have necessary OS privileges
# Produces even less compliant HTML than plshprof, but all browsers display it nicely.
HPROF_PATH_NAME=$1
HPROF_ROOT_FILE=$2
HPROF_ROOT_FILE_PURE=${HPROF_ROOT_FILE%.*}

cd ${HPROF_PATH_NAME}

${ORACLE_BIN}/plshprof -output ${HPROF_ROOT_FILE_PURE} ${HPROF_ROOT_FILE}

HPROF_INTM_FILE="${HPROF_ROOT_FILE_PURE}"_intm
HPROF_AGGR_FILE="${HPROF_ROOT_FILE_PURE}"_aggr.html

BACK_TOP="<H2 align=\"center\"> <A HREF = ${HPROF_ROOT_FILE_PURE}.html> ↑ Back To Top ↑ </A></H2>"

echo "<P ID=${HPROF_ROOT_FILE_PURE}.html>" > "${HPROF_INTM_FILE}"
cat "${HPROF_ROOT_FILE_PURE}".html >> "${HPROF_INTM_FILE}"
echo "${BACK_TOP}" >> "${HPROF_INTM_FILE}"

keys="ts tf fn td tc ms mf md 2f 2n 2c nsf nsp nsc pc"
for key in $keys
do
    echo "<P ID=${HPROF_ROOT_FILE_PURE}_${key}.html>" >> "${HPROF_INTM_FILE}"
    cat "${HPROF_ROOT_FILE_PURE}"_${key}.html >> "${HPROF_INTM_FILE}"
    echo "${BACK_TOP}" >> "${HPROF_INTM_FILE}"
done

rm "${HPROF_ROOT_FILE_PURE}"_*.html
rm "${HPROF_ROOT_FILE_PURE}".html

sed -e "s/<A HREF = \([^#]\)/<A HREF = #\1/g" \
    -e "s/#"${HPROF_ROOT_FILE_PURE}"_pc.html#/#/g" \
    "${HPROF_INTM_FILE}" > "${HPROF_AGGR_FILE}"

rm "${HPROF_INTM_FILE}"

mv ${HPROF_AGGR_FILE} ${HPROF_ROOT_FILE_PURE}.html
It seems that Oracle 12.2.0.1.0 is trying to incorporate my idea/script into its new plshprof.


2. SQL ID Elapsed Time (microsecs) Data sorted by SQL ID


A new Section:
     SQL ID Elapsed Time (microsecs) Data sorted by SQL ID
is added, which contains:
     SQL ID      SQL TEXT
for SQL statements and dynamic_string in EXECUTE IMMEDIATE Statement (SQL TEXT is cut off to a length of 50).


3. Memory fault in 12.2.0.1.0


Oracle MOS:
     Bug 26789742 : NEW LINE IN DYNAMIC STRING MAKES DBMS_HPROF TO CRASH WITH ORA 3113/7445
reveals Memory fault/SIGSEGV exceptions when there exist new line characters in raw profiler output files.

This BUG is probably related to the above 12cR2 new introduced Section:
     SQL ID Elapsed Time (microsecs) Data sorted by SQL ID
in which "SQL TEXT" could be a dynamic_string containing new line characters.

As documented in Bug 26789742, the Bug can be reproduced with following test:

declare
  l_plshprof_dir     varchar2(20) := 'PLSHPROF_DIR';
  l_filename         varchar2(50) := 'hprof_Bug_26789742.trc';
  l_txt              varchar2(50);
begin
  dbms_hprof.start_profiling(l_plshprof_dir, l_filename, null, true, true);
  
  --put a New Line in the start of dynamic_string.
  execute immediate 
q'[

select 'New Line Bug 26789742 Demo' from dual

]' into l_txt;
  dbms_hprof.stop_profiling;
  
  dbms_output.put_line(l_txt);
 
  --analyzed HTML report in location/filename.html
  --Bug 26789742
  dbms_hprof.analyze(location => l_plshprof_dir, filename => l_filename);
end;
/
Process (Session) abnormally terminated, and trace/incident file showed:

ORA-07445: exception encountered: 
   core dump [PrintSymbolEntriesSymbol1P()+82] [SIGSEGV] 
   [ADDR:0x0] [PC:0xF4405A2] [Address not mapped to object] []
12.2.0.1.0 Test

$ORACLE_HOME/bin/plshprof -output test_4mb test_4mb.trc

PLSHPROF: Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
[7223 symbols processed]
Memory fault
12.1.0.2.0 Test

$ORACLE_HOME/bin/plshprof -output test_4mb test_4mb.trc

PLSHPROF: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
[7223 symbols processed]
[Report written to 'test_4mb.html']


4. New APIs


dbms_hprof in 12cR2 is enhanced with 3 new APIs, demonstrated as follows:

declare
  l_plshprof_dir     varchar2(20) := 'PLSHPROF_DIR';
  l_filename         varchar2(20) := 'hprof_ksun_1.trc';
  l_cnt              number;
  l_sqlmonitor_repot clob;
  l_report_clob      clob;
begin
  dbms_hprof.start_profiling(l_plshprof_dir, l_filename, null, true, true);
  select 123 into l_cnt from dual;
  l_sqlmonitor_repot := dbms_hprof.stop_profiling;
  
  --Real-Time Monitoring report for the profiler run.
  dbms_output.put_line('<!--sqlmonitor_report-->' || l_sqlmonitor_repot);
  
  --analyzed HTML report in location/filename.html
  dbms_hprof.analyze(location => l_plshprof_dir, filename => l_filename);
  
  --analyzed HTML report in report_clob.
  dbms_hprof.analyze(location => l_plshprof_dir, filename => l_filename, report_clob => l_report_clob);
  dbms_output.put_line('<!--hprof_report-->' || l_report_clob);
end;
/

Tuesday, July 3, 2018

12c Online Statistics Gathering for Direct Path Insert and 3 Side Effects

Oracle 12c introduced Online Statistics Gathering for direct path Bulk Loads (CTAS and IAS), documented in:
    Online Statistics Gathering for Bulk Loads 
    Online Statistics Gathering 
This Blog will discuss its side effects in 3 aspects:
    Read Committed (default transaction isolation level)
    Global Temporary Table (GTT)
    Deadlock with MMON Slave Process (Mnnn)
Note: All tests are done in Oracle 12.1.0.2.0 and 12.2.0.1.0


1. Online Statistics Gathering


xplan is adjusted with one additional Row Source line marked as "OPTIMIZER STATISTICS GATHERING". And it is exposed in DBA_TAB_COL_STATISTICS.NOTES column as "STATS_ON_LOAD ".

SQL(100) > drop table test_tab;

SQL(100) > create table test_tab(x number, y number);

SQL(100) > explain plan for insert /*+ append */ into test_tab select level x, mod(level, 4) y from dual connect by level <= 4321;

SQL(100) > select * from table(dbms_xplan.display);

    ---------------------------------------------------------------------------------------
    Plan hash value: 1541388231
    
    -------------------------------------------------------------------------------------
    | Id  | Operation                        | Name     | Rows  | Cost (%CPU)| Time     |
    -------------------------------------------------------------------------------------
    |   0 | INSERT STATEMENT                 |          |     1 |     2   (0)| 00:00:01 |
    |   1 |  LOAD AS SELECT                  | TEST_TAB |       |            |          |
    |   2 |   OPTIMIZER STATISTICS GATHERING |          |     1 |     2   (0)| 00:00:01 |
    |*  3 |    CONNECT BY WITHOUT FILTERING  |          |       |            |          |
    |   4 |     FAST DUAL                    |          |     1 |     2   (0)| 00:00:01 |
    -------------------------------------------------------------------------------------

SQL(100) > insert /*+ append */ into test_tab select level x, mod(level, 4) y from dual connect by level <= 4321;

SQL(100) > select owner, table_name, column_name, num_distinct, last_analyzed, '"'||notes||'"'
           from dba_tab_col_statistics 
           where table_name in ('TEST_TAB') and notes like '%STATS_ON_LOAD%';

    OWNER  TABLE_NAME  COLUMN_NAME  NUM_DISTINCT  LAST_ANALYZED         NOTES
    ------ ----------- ------------ ------------- --------------------- ----------------
    K      TEST_TAB    X            4321          2018-JUL-03 08:08:09  "STATS_ON_LOAD "
    K      TEST_TAB    Y            4             2018-JUL-03 08:08:09  "STATS_ON_LOAD "

SQL(100) > commit;
Same as other new features, Oracle provided 3 ways to disable it:
    Statement level hints: 
        no_gather_optimizer_statistics
      or 
        OPT_PARAM('_optimizer_gather_stats_on_load' 'false') 
    Session level: 
        alter session set "_optimizer_gather_stats_on_load"=false;
    System level:
        alter system set "_optimizer_gather_stats_on_load"=false;


2. Read Committed Violation


At first, look the new behaviour of Read Committed as Oracle default transaction isolation level:

---========= (1-1): Session_1@T1 =========---
SQL(100) > drop table test_tab;

SQL(100) > create table test_tab(x number, y number);

SQL(100) > insert /*+ append */ into test_tab select level x, mod(level, 4) y from dual connect by level <= 4321;
    4321 rows created.

---========= (1-2): Session_2@T2 =========---
         
SQL(200) > select table_name, num_rows, last_analyzed from dba_tables where table_name in ('TEST_TAB');
    TABLE_NAME     NUM_ROWS LAST_ANALYZED
    ------------ ---------- --------------------
    TEST_TAB           4321 2018*JUN*29 10:52:41

SQL(200) > explain plan for select * from test_tab; 

SQL(200) > select * from table(dbms_xplan.display);

    ------------------------------------------------------------------------------
    | Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |          |  4321 | 30247 |    30   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS FULL| TEST_TAB |  4321 | 30247 |    30   (0)| 00:00:01 |
    ------------------------------------------------------------------------------      
       
---========= (1-3): Session_1@T3 =========---         
SQL(100) > rollback;

SQL(100) > insert /*+ append */ into test_tab select level x, mod(level, 4) y from dual connect by level <= 1234;
    1234 rows created.
             
---========= (1-4): Session_2@T4 =========---
SQL(200) > select table_name, num_rows, last_analyzed from dba_tables where table_name in ('TEST_TAB');
    TABLE_NAME     NUM_ROWS LAST_ANALYZED
    ------------ ---------- -------------
    TEST_TAB

SQL(200) > explain plan for select * from test_tab; 

SQL(200) > select * from table(dbms_xplan.display);

    ------------------------------------------------------------------------------
    | Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |          |    82 |   574 |     2   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS FULL| TEST_TAB |    82 |   574 |     2   (0)| 00:00:01 |
    ------------------------------------------------------------------------------
Above tests showed that the second session can see the exact number (4321) of inserted rows from first session before committed, hence "Read Committed" broken.

If rollback IAS and insert again, there is no more Online Statistics Gathering. So this feature is only waked up on the first IAS.

As a consequence, Xplan is created based on un-committed data, potentially volatile xplan and unstable performance.

Continue our test, if we truncate the table and insert again (also without commit), Online Statistics Gathering recurs again as demonstrated below:

---========= (1-5): Session_1@T5 =========---         

SQL(100) > truncate table test_tab;

SQL(100) > insert /*+ append */ into test_tab select level x, mod(level, 4) y from dual connect by level <= 9876;

---========= (1-6): Session_2@T6 =========---

SQL(200) > select table_name, num_rows, last_analyzed from dba_tables where table_name in ('TEST_TAB');
    TABLE_NAME     NUM_ROWS LAST_ANALYZED
    ------------ ---------- --------------------
    TEST_TAB           9876 2018*JUN*29 10:58:41

SQL(200) > explain plan for select * from test_tab; 

SQL(200) > select * from table(dbms_xplan.display);

    ------------------------------------------------------------------------------
    | Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |          |  9876 | 69132 |     4   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS FULL| TEST_TAB |  9876 | 69132 |     4   (0)| 00:00:01 |
    ------------------------------------------------------------------------------


3. Global Temporary Table


Repeat the same test for GTT with commit delete rows.

---========= (2-1): Session_1@T7 =========---         
SQL(100) > rollback;

SQL(100) > drop table test_tab;

SQL(100) > create global temporary table test_tab(x number, y number) on commit delete rows;  

SQL(100) > insert /*+ append */ into test_tab select level x, mod(level, 4) y from dual connect by level <= 4321;
    4321 rows created.


---========= (2-2): Session_2@T8 =========---
         
SQL(200) > select table_name, num_rows, last_analyzed from dba_tables where table_name in ('TEST_TAB');
    TABLE_NAME     NUM_ROWS LAST_ANALYZED
    ------------ ---------- --------------------
    TEST_TAB           4321 2018*JUN*29 11:19:36

SQL(200) > explain plan for select * from test_tab; 

SQL(200) > select * from table(dbms_xplan.display);

    ------------------------------------------------------------------------------
    | Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |          |  4321 | 30247 |    30   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS FULL| TEST_TAB |  4321 | 30247 |    30   (0)| 00:00:01 |
    ------------------------------------------------------------------------------      

---========= (2-3): Session_1@T9 =========---         
SQL(100) > rollback;

SQL(100) > insert /*+ append */ into test_tab select level x, mod(level, 4) y from dual connect by level <= 1234;
    1234 rows created.
             
---========= (2-4): Session_2@T10 =========---
SQL(200) > select table_name, num_rows, last_analyzed from dba_tables where table_name in ('TEST_TAB');
    TABLE_NAME     NUM_ROWS LAST_ANALYZED
    ------------ ---------- --------------------
    TEST_TAB           1234 2018*JUN*29 11:21:16

SQL(200) > explain plan for select * from test_tab; 

SQL(200) > select * from table(dbms_xplan.display);

    ------------------------------------------------------------------------------
    | Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |          |  1234 |  8638 |    30   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS FULL| TEST_TAB |  1234 |  8638 |    30   (0)| 00:00:01 |
    ------------------------------------------------------------------------------
For GTT, other session can also see the exact number (4321) of inserted rows, hence GTT is no more session private.

In contrary to normal table, if rollback IAS and insert again, Online Statistics is re-gathered.

If GTT is created with commit preserve rows, the behavior is similar to normal table.

However Oracle 12c Documentation said that bulk loads do not gather statistics automatically when target table is global temporary table defined as ON COMMIT DELETE ROWS (see Online Statistics Gathering for Bulk Loads ).

The default GTT behaviour ('SHARED') can be turned off by global or table prefs:

exec dbms_stats.set_global_prefs ('GLOBAL_TEMP_TABLE_STATS','SESSION');
exec dbms_stats.set_table_prefs(null, 'TEST_TAB', 'GLOBAL_TEMP_TABLE_STATS', 'SESSION');

select dbms_stats.get_prefs('GLOBAL_TEMP_TABLE_STATS') from dual;


4. Internals


Turn on SQL Trace (10046), perform 3 IAS operations and one CTAS, trace files show the call of dbms_stats.postprocess_stats, which is probably gathering Online Statistics.

IAS - insert
  BEGIN dbms_stats.postprocess_stats(:owner, :tabname, :partname, :subpartname, 2, :rawstats, :selmap, :clist); END; 

IAS - commit
  BEGIN dbms_stats.postprocess_stats(:owner, :tabname, :partname, :subpartname, 1, null, null, null); END; 

IAS - rollback
  BEGIN dbms_stats.postprocess_stats(:owner, :tabname, :partname, :subpartname, 8, null, null, null); END; 

CTAS
  create table test_tab as select level x, mod(level, 4) y from dual connect by level <= 4321;
  BEGIN dbms_stats.postprocess_stats(:owner, :tabname, :partname, :subpartname, 6, :rawstats, :selmap, :clist);  END;   
It looks like that Statistics gathering is controlled by different FLAG (2, 1, 8, 6), where CTAS 6 = 2 + 4.

With dbms_stats Tracing, we can also observe this call:

SQL(300) > execute dbms_stats.set_global_prefs('trace',to_char(1+4+16+2048+65536)); 

SQL(300) > select sname, sval2, spare4 from sys.optstat_hist_control$ where sname in ('TRACE');
    SNAME      SVAL2                          SPARE4
    ---------- ------------------------------ ------
    TRACE      2018-JUN-29 12:10:36 +02:00    67605

SQL(300) > drop table test_tab;
SQL(300) > create table test_tab as select level x, mod(level, 4) y from dual connect by level <= 4321;

  DBMS_STATS: postprocess_stats on KS.TEST_TAB.. tobjn: 2585597 fobjn:
  DBMS_STATS: postprocess online optimizer stats gathering for KS.TEST_TAB: save statistics


5. Deadlock


There exist one background MMON Slave Process (Mnnn, for instance, M002), which is periodically (each 15 minutes in default) spawned to update Online Statistics (Flush KSXM hash table action). If coincidentally one foreground User session performs an IAS or CTAS operation at the same time, it can cause some deadlock as follows (some text removed):

Deadlock graph:
                ---------Blocker(s)--------  ---------Waiter(s)---------
Resource Name   process session holds waits  process session holds waits
TX-001F0003     123     321     X            789    987      S
TX-004C0008     789     987     X            123    321      X

Rows waited on:
  Session 321: obj - rowid = 00000001 
  (dictionary objn - 1, file - 51, block - 2012345, slot - 0)
  Session 987: no row
 
----- Information for the OTHER waiting sessions -----
Session 987:
  sid: 987 ser: 9876 audsid: 0 user: 0/SYS
    image: oracle@testdb (M002)
  current SQL:
    merge /*+ dynamic_sampling(mm 4) dynamic_sampling_est_cdn(mm) dynamic_sampling(m 4) dynamic_sampling_est_cdn(m) */ into sys.mon_mods_all$ mm
 
Information for THIS session:
----- Current SQL Statement for this session (sql_id=cyz7q9n00p23a) -----
update optstat_hist_control$ set sval2 = systimestamp  where sname = 'MON_MODS_ALL_UPD_TIME'   
If we turn on following sql_trace:

alter system set events 'sql_trace [sql:5vf82wpjq5y0v] wait=true, bind=true,plan_stat=first_execution,level=12';

alter system set events 'sql_trace [sql:cyz7q9n00p23a] wait=true, bind=true,plan_stat=first_execution,level=12';

alter system set events 'sql_trace [sql:aahsjvcnr3swn] wait=true, bind=true,plan_stat=first_execution,level=12';
and look both trace files (see Appendix: sql_fulltext):

Unix process pid: 123, image: oracle@testdb (M002)
  *** ACTION NAME:(Flush KSXM hash table action) 2018-06-26 11:15:27.632
 
===================== sql_id=5vf82wpjq5y0v 
select null from optstat_hist_control$ 
where sname = 'MON_MODS_ALL_UPD_TIME' 
  and ((systimestamp - sval2) > INTERVAL '540' SECOND or sval2 is null)
for update

===================== sql_id=aahsjvcnr3swn
merge /*+ dynamic_sampling(mm 4) dynamic_sampling_est_cdn(mm) ynamic_sampling(m 4) dynamic_sampling_est_cdn(m) */                    
 into sys.mon_mods_all$ mm 
 
===================== sql_id=cyz7q9n00p23a 
update optstat_hist_control$ 
   set sval2 = systimestamp where sname = 'MON_MODS_ALL_UPD_TIME'

Unix process pid: 456, image: oracle@testdb

===================== sql_id=aahsjvcnr3swn
merge /*+ dynamic_sampling(mm 4) dynamic_sampling_est_cdn(mm) ynamic_sampling(m 4) dynamic_sampling_est_cdn(m) */                    
 into sys.mon_mods_all$ mm 
 
===================== sql_id=cyz7q9n00p23a 
update optstat_hist_control$ 
   set sval2 = systimestamp where sname = 'MON_MODS_ALL_UPD_TIME'
The above trace files showed that M002 performs:
  select from optstat_hist_control$ for update
  merge into sys.mon_mods_all$
  update optstat_hist_control$ 
whereas User session executes:
  merge into sys.mon_mods_all$
  update optstat_hist_control$ 
The interleaved DML calls on mon_mods_all$ and optstat_hist_control$ probably caused the deadlock.

For all ORA-00060, the trace file is printed with following notice:
  The following deadlock is not an ORACLE error. It is a
  deadlock due to user error in the design of an application
  or from issuing incorrect ad-hoc SQL. The following
  information may aid in determining the deadlock:
In this case, the standard message is probably not addressed to application users.

Oracle has a hidden parameter (existed already in Oracle 11g):

   _dml_monitoring_enabled 
to toggle modification monitoring. The default value is TRUE, i.e. enabled.
The workaround could be:

   alter system set "_dml_monitoring_enabled"=false;
but MOS: "Bug 20559144 - DML Monitoring can only be disabled with STATISTICS_LEVEL (Doc ID 20559144.8)" said:
    dml monitoring is happens even with _dml_monitoring_enabled set to false
and futher Workaround is:

    statistics_level=basic
We have tested that only setting _dml_monitoring_enabled=false, and leaving statistics_level=typical dml monitoring still occurs. Once setting _dml_monitoring_enabled=false, and statistics_level=basic there is no more dml monitoring. These can be checked by reading MMON Slave Process (Mnnn) traces or running query below, and observing executions increasing, last_active_time progressing:
  
   select sql_id, executions, last_active_time, first_load_time, sql_text
   from v$sql v 
   where sql_id in ('5vf82wpjq5y0v', 'cyz7q9n00p23a', 'aahsjvcnr3swn')
   order by v.last_active_time desc;   


6. Appendix: sql_fulltext


Here the complete sqltext of above involved statements:

--sql_id=5vf82wpjq5y0v 
 select null
   from optstat_hist_control$
  where     sname = 'MON_MODS_ALL_UPD_TIME'
        and ( (systimestamp - sval2) > interval '540' second or sval2 is null)
 for update

--sql_id=cyz7q9n00p23a 
update sys.optstat_hist_control$ set sval2 = systimestamp  where sname = 'MON_MODS_ALL_UPD_TIME';

--sql_id=aahsjvcnr3swn  (M002)
merge /*+ dynamic_sampling(mm 4) dynamic_sampling_est_cdn(mm) dynamic_sampling(m 4) dynamic_sampling_est_cdn(m) */
     into  sys.mon_mods_all$ mm
     using (select m.obj#          obj#,
                   m.inserts       inserts,
                   m.updates       updates,
                   m.deletes       deletes,
                   m.flags         flags,
                   m.timestamp     timestamp,
                   m.drop_segments drop_segments
              from sys.mon_mods$ m, tab$ t
             where m.obj# = t.obj# and :objn = 0
            union all
            select m.obj#          obj#,
                   m.inserts       inserts,
                   m.updates       updates,
                   m.deletes       deletes,
                   m.flags         flags,
                   m.timestamp     timestamp,
                   m.drop_segments drop_segments
              from sys.mon_mods$ m, tab$ t
             where m.obj# = t.obj# and :objn > 0 and m.obj# = :objn) v
        on (mm.obj# = v.obj#)
when matched
then
  update set mm.inserts = mm.inserts + v.inserts,
             mm.updates = mm.updates + v.updates,
             mm.deletes = mm.deletes + v.deletes,
             mm.flags = mm.flags + v.flags - bitand (mm.flags, v.flags) /* bitor(mm.flags,v.flags) */,
             mm.timestamp = v.timestamp,
             mm.drop_segments = mm.drop_segments + v.drop_segments
when not matched
then
  insert     (obj#,
              inserts,
              updates,
              deletes,
              timestamp,
              flags,
              drop_segments)
      values (v.obj#,
              v.inserts,
              v.updates,
              v.deletes,
              sysdate,
              v.flags,
              v.drop_segments);