Wednesday, January 24, 2018

Oracle Physical Read Access Path and Cost

In this Blog, we will look into Physical Read (Disk Read, DB File Read) in different Access Paths and their Cost.

We will first test 3 varieties of db file read:
  db file sequential read
  db file scattered read 
  db file parallel read  
and then compare the cost (statistics) reported in 3 places for different access path:
  SQL Trace
  Dtrace
  Oracle View: V$FILESTAT and V$IOSTAT_FILE
Note: All tests are done in Oracle 12.1.0.2 on Solaris.


1. DB File Read Access Path


Run 4 variants of Access Path tests with SQl Trace (Event 10046), and at the same time dtace the process (see appended Dtrace Script).


1.1. single Read



SQL > exec db_file_read_test('single', 1, 333);

-- adjacent rowid, single block read, 'db file sequential read'
SELECT /*+ single_read */ Y FROM KSUN_TAB T WHERE ROWID = :B1 

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute    333      0.01       0.01          0          0          0           0
Fetch      333      0.01       0.01        641        333          0         333
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      667      0.02       0.02        641        333          0         333

Row Source Operation
---------------------------------------------------
TABLE ACCESS BY USER ROWID KSUN_TAB (cr=1 pr=8 pw=0 time=160 us cost=1 size=3513 card=1)

  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file scattered read                         44        0.00          0.00
  db file sequential read                       289        0.00          0.00
 
------------------------------ dtrace ------------------------------
  PROBEFUNC      FD  RETURN_SIZE     COUNT
  lseek         260            0        44
  readv         260        65536        44
  pread         260         8192       289

  PROBEFUNC      FD  MAX_READ_Blocks
  pread         260                1
  readv         260                8

TOTAL_SIZE = 5251072   , TOTAL_READ_Blocks = 641   , TOTAL_READ_CNT = 333

readv         260
           value  ------------- Distribution ------------- count
            8192 |                                         0
           16384 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@  43
           32768 |@                                        1
           65536 |                                         0


pread         260
           value  ------------- Distribution ------------- count
            2048 |                                         0
            4096 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@          226
            8192 |@@@@@@@@                                 60
           16384 |                                         2
           32768 |                                         0
           65536 |                                         1
          131072 |                                         0


1.2. scattered Read



SQL > exec db_file_read_test('scattered', 1, 333);

-- jumped rowid, scattered read, 'db file scattered read'
SELECT /*+ scattered_read */ Y FROM KSUN_TAB T WHERE ROWID = :B1 

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute    333      0.00       0.00          0          0          0           0
Fetch      333      0.02       0.02       2664        333          0         333
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      667      0.02       0.02       2664        333          0         333


Row Source Operation
---------------------------------------------------
TABLE ACCESS BY USER ROWID KSUN_TAB (cr=1 pr=8 pw=0 time=156 us cost=1 size=3513 card=1)

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file scattered read                        333        0.00          0.00
  
------------------------------ dtrace ------------------------------
  PROBEFUNC      FD  RETURN_SIZE     COUNT
  lseek         260            0        91
  readv         260        65536       333

  PROBEFUNC      FD  MAX_READ_Blocks
  readv         260                8

TOTAL_SIZE = 21823488  , TOTAL_READ_Blocks = 2664  , TOTAL_READ_CNT = 333

readv         260
           value  ------------- Distribution ------------- count
            8192 |                                         0
           16384 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      290
           32768 |@@@@@                                    43
           65536 |                                         0


1.3. parallel Read



SQL > exec db_file_read_test('parallel', 1, 333);

SELECT /*+ index(t ksun_tab#i1) parallel_read */ MAX(Y) FROM KSUN_TAB T WHERE X BETWEEN 1 AND :B1 

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00        344        335          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00        344        335          0           1

Row Source Operation
---------------------------------------------------
SORT AGGREGATE (cr=335 pr=344 pw=0 time=3760 us)
 FILTER  (cr=335 pr=344 pw=0 time=1698 us)
  TABLE ACCESS BY INDEX ROWID BATCHED KSUN_TAB (cr=335 pr=344 pw=0 time=1361 us cost=168 size=1167165 card=333)
   INDEX RANGE SCAN KSUN_TAB#I1 (cr=2 pr=8 pw=0 time=279 us cost=1 size=0 card=333)(object id 2260477)

  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file scattered read                          4        0.00          0.00
  db file parallel read                           2        0.00          0.00
  
 -- Raw Trace File --
 'db file scattered read' ela= 49  file#=917 block#=10368 blocks=8     obj#=2260477 (Index KSUN_TAB#I1)
 'db file scattered read' ela= 27  file#=917 block#=128   blocks=8     obj#=2260476 (Table KSUN_TAB) 
 'db file scattered read' ela= 21  file#=917 block#=136   blocks=8     obj#=2260476
 'db file parallel read'  ela= 422 files=1   blocks=127   requests=127 obj#=2260476
 'db file parallel read'  ela= 334 files=1   blocks=127   requests=127 obj#=2260476
 'db file scattered read' ela= 264 file#=917 block#=409   blocks=66    obj#=2260476
  
------------------------------ dtrace ------------------------------
   PROBEFUNC      FD  RETURN_SIZE     COUNT
   pread         260       540672         1
   lseek         260            0         2
   readv         260        65536         3
   pread         260         8192       254
 
   PROBEFUNC      FD  MAX_READ_Blocks
   readv         260                8
   pread         260               66
 
 TOTAL_SIZE = 2818048   , TOTAL_READ_Blocks = 344   , TOTAL_READ_CNT = 258
 
   readv         260
            value  ------------- Distribution ------------- count
             8192 |                                         0
            16384 |@@@@@@@@@@@@@@@@@@@@@@@@@@@              2
            32768 |@@@@@@@@@@@@@                            1
            65536 |                                         0
 
   pread         260
            value  ------------- Distribution ------------- count
             2048 |                                         0
             4096 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@          196
             8192 |@@@@@@@@                                 52
            16384 |                                         2
            32768 |                                         0
            65536 |@                                        4
           131072 |                                         1
           262144 |                                         0


1.4. full Read



SQL > exec db_file_read_test('full', 1, 333);

SELECT /*+ full_read */ MAX(Y) FROM KSUN_TAB T WHERE ROWNUM <= :B1 

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.00        342        342          3           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.00        342        342          3           1

Row Source Operation
---------------------------------------------------
SORT AGGREGATE (cr=342 pr=342 pw=0 time=3788 us)
 COUNT STOPKEY (cr=342 pr=342 pw=0 time=1371 us)
  TABLE ACCESS FULL KSUN_TAB (cr=342 pr=342 pw=0 time=925 us cost=99 size=1169334 card=334)

  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file sequential read                         2        0.00          0.00
  db file scattered read                         23        0.00          0.00

-- Raw Trace File --
'db file sequential read' ela= 19 file#=917 block#=130 blocks=1 obj#=2260476 tim=647299975335
'db file sequential read' ela= 14 file#=3 block#=768 blocks=1 obj#=0 tim=647299975399
     -- UNDO file#=3   /oratestdb/oradata/testdb/undo01.dbf
'db file scattered read' ela= 22 file#=917 block#=131 blocks=5 obj#=2260476 tim=647299975501
'db file scattered read' ela= 25 file#=917 block#=136 blocks=8 obj#=2260476 tim=647299975609
'db file scattered read' ela= 25 file#=917 block#=145 blocks=7 obj#=2260476 tim=647299975713
'db file scattered read' ela= 23 file#=917 block#=152 blocks=8 obj#=2260476 tim=647299975806
'db file scattered read' ela= 25 file#=917 block#=161 blocks=7 obj#=2260476 tim=647299975901
'db file scattered read' ela= 24 file#=917 block#=168 blocks=8 obj#=2260476 tim=647299975994
'db file scattered read' ela= 23 file#=917 block#=177 blocks=7 obj#=2260476 tim=647299976088
'db file scattered read' ela= 23 file#=917 block#=184 blocks=8 obj#=2260476 tim=647299976178
'db file scattered read' ela= 23 file#=917 block#=193 blocks=7 obj#=2260476 tim=647299976270
'db file scattered read' ela= 22 file#=917 block#=200 blocks=8 obj#=2260476 tim=647299976364
'db file scattered read' ela= 23 file#=917 block#=209 blocks=7 obj#=2260476 tim=647299976465
'db file scattered read' ela= 22 file#=917 block#=216 blocks=8 obj#=2260476 tim=647299976554
'db file scattered read' ela= 22 file#=917 block#=225 blocks=7 obj#=2260476 tim=647299976646
'db file scattered read' ela= 29 file#=917 block#=232 blocks=8 obj#=2260476 tim=647299976759
'db file scattered read' ela= 24 file#=917 block#=241 blocks=7 obj#=2260476 tim=647299976866
'db file scattered read' ela= 23 file#=917 block#=248 blocks=8 obj#=2260476 tim=647299976956
'db file scattered read' ela= 128 file#=917 block#=258 blocks=32 obj#=2260476 tim=647299977200
'db file scattered read' ela= 95 file#=917 block#=290 blocks=32 obj#=2260476 tim=647299977511
'db file scattered read' ela= 97 file#=917 block#=322 blocks=32 obj#=2260476 tim=647299977822
'db file scattered read' ela= 87 file#=917 block#=354 blocks=30 obj#=2260476 tim=647299978113
'db file scattered read' ela= 96 file#=917 block#=386 blocks=32 obj#=2260476 tim=647299978407
'db file scattered read' ela= 108 file#=917 block#=418 blocks=32 obj#=2260476 tim=647299978719
'db file scattered read' ela= 94 file#=917 block#=450 blocks=32 obj#=2260476 tim=647299979021

------------------------------ dtrace ------------------------------
   PROBEFUNC      FD  RETURN_SIZE     COUNT
   pread         260         8192         1
   pread         260       245760         1
   readv         260        40960         1
   pread         260       262144         6
   readv         260        57344         7
   lseek         260            0         8
   readv         260        65536         8
 
   PROBEFUNC      FD  MAX_READ_Blocks
   readv         260                8
   pread         260               32
 
 TOTAL_SIZE = 2793472   , TOTAL_READ_Blocks = 341   , TOTAL_READ_CNT = 24
 
   readv         260
            value  ------------- Distribution ------------- count
             8192 |                                         0
            16384 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 16
            32768 |                                         0
 
   pread         260
            value  ------------- Distribution ------------- count
            16384 |                                         0
            32768 |@@@@@@@@@@@@@@@@@@@@                     4
            65536 |@@@@@@@@@@@@@@@                          3
           131072 |@@@@@                                    1
           262144 |                                         0

*Note: One extra Read in SQL Trace: 25 (=23+2) Reads with 342 Blocks in SQL Trace vs. 
       dtrace 24 Reads with 341 Blocks is due to one UNDO Read (file#=3).


2. UNIX References


At first some documentations from UNIX man pages and internet.
  ssize_t pread(int fildes, void * buf, size_t nbyte, off_t offset);
  ssize_t readv(int fildes, struct iovec * iov, int iovcnt);
  off_t lseek(int fildes, off_t offset, int whence)
pread(): performs the same action as read(), except that it reads from a given position in the file 
         without changing the file pointer. The first three arguments to pread() are the same as read() with the addition of 
         a fourth argument offset for the desired position inside the file. pread() will read up to the maximum offset value 
         that can be represented in an off_t for regular files.

readv(): is equivalent to read(), but places the input data into the iovcnt buffers 
         specified by the members of the iov array: iov 0 , iov 1 , ..., iov [ iovcnt -1]. 
         The iovcnt argument is valid if greater than 0 and less than or equal to IOV_MAX .
         
         The iovec structure contains the following members:
             caddr_t   iov_base;
             int       iov_len;  
         Each iovec entry specifies the base address and length of an area in memory where data should be placed. 
         The readv() function always fills an area completely before proceeding to the next.
         
         Upon successful completion, readv() marks for update the st_atime field of the file.
         
         IOV_MAX: 1024 On Linux, 16 On Solaris, 16 On AIX and HP-UX
            
readv (Linux): Read data from file descriptor FD, and put the result in the buffers described by VECTOR, 
         which is a vector of COUNT 'struct iovec's. The buffers are filled in the order specified. 
         Operates just like 'read' (see <unistd.h>) except that data are put in VECTOR instead of a contiguous buffer.    

lseek(): sets the file pointer associated with the open file descriptor specified by fildes.


3. Oracle References


db file sequential read: P1 = file#, P2 = block#, P3 = blocks
 file#:  This is the file# of the file that Oracle is trying to read from. From Oracle8 onwards it is the ABSOLUTE file number (AFN).
 block#: This is the starting block number in the file from where Oracle starts reading the blocks. Typically only one block is being read.
 blocks: This parameter specifies the number of blocks that Oracle is trying to read from the file# starting at block#. This is
   usually "1" but if P3 > 1 then this is a multiblock read. Multiblock "db file sequential read"s may be seen in 
   earlier Oracle versions when reading from a SORT (TEMPORARY) segments. 
   
db file scattered read:  P1 = file#, P2 = block#, P3 = blocks
 file#:  This is the file# of the file that Oracle is trying to read from. In Oracle8 onwards it is the ABSOLUTE file number (AFN).
 block#: This is the starting block number in the file from where Oracle starts reading the blocks. 
 blocks: This parameter specifies the number of blocks that Oracle is trying to read from the file# starting at block#. 
   The upper limit is DB_FILE_MULTIBLOCK_READ_COUNT, which is self tuned from Oracle 10.2 onwards.
                
db file parallel read:   P1 = files, P2 = blocks, P3 = requests
 files:    This indicates the number of files to which the session is reading
 blocks:   This indicates the total number of blocks to be read
 requests: This indicates the total number of I/O requests, which will be the same as blocks
 
WAITEVENT: "db file sequential read" Reference Note (Doc ID 34559.1)
 This signifies a wait for an I/O read request to complete. This call differs from "db file scattered read" 
 in that a sequential read reads data into contiguous memory (whilst a scattered read reads multiple blocks 
 and scatters them into different buffers in the SGA). 

WAITEVENT: "db file scattered read" Reference Note (Doc ID 34558.1) 
 This wait happens when a session is waiting for a multiblock IO to complete. This typically occurs during 
 FULL TABLE SCANs or INDEX FAST FULL SCANs. Oracle reads up to DB_FILE_MULTIBLOCK_READ_COUNT consecutive blocks 
 at a time and scatters them into buffers in the buffer cache.
  
C.3.34 db file parallel read
 This happens during recovery. It can also happen during buffer prefetching, 
 as an optimization (rather than performing multiple single-block reads). 


4. Discussions


(a). pread and readv: both read contiguous file space. pread places the input data into one single contiguous buffer (memory space);
     whereas readv distributes them into multi buffers.
     
     It looks like that the difference between pread and readv is the difference of memory allocation. Their disk operations are the same.
     
     pread specifies file reading position by a third parameter: offset;
     whereas readv requires a precedent lseak (with parameter: offset) to fix the file reading position.

     Contiguous file space means logically contiguous in a file, but does not mean physically contiguous in a disk.
     Oracle 12 Logical Storage Structures Section: Overview of Extents wrote:
          An extent is a unit of database storage made up of logically contiguous data blocks. Data blocks can be physically spread out 
             on disk because of RAID striping and file system implementations.

(b). pread can fulfill all 3 kinds of db file read:
         db file sequential read (Test 1.1. single Read)
         db file scattered read  (Test 1.3. parallel Read, see next discussion)
         db file parallel read   (Test 1.3. parallel Read, see next discussion)
     whereas readv is for
         db file scattered read  (Test 1.2. scattered Read)  

(c). In all three tests of "single Read", "scattered Read" and "parallel Read", 
     SQL Trace shows "disk" is bigger than "query" (641 > 333,  2664 > 333, 344 > 335), 
     there seems some wastage since more disk Blocks are read than consumed.
     
     But the number of "disk" read Requests, which is gathered by dtrace TOTAL_READ_CNT, is no more than 
     SQL Trace "disk" and "query". From performance point of view, number of "disk" read Requests is one more cost factor
     than number of read Blocks. This is visible in dtrace quantize (frequency distribution diagram) output, 
     where the value field denotes elapsed nanoseconds. For example, in Test "single Read", elapsed time per Block read 
     for readv (8 Blocks per read Request), and pread (1 Block per read Request) can be calculated as:     
     
       readv: (16384*43 + 32768*1)/8/44 = 2094 
       pread: (4096*226 + 8192*60 + 16384*2 + 65536*1)/289 = 5244 

(d). 'db file parallel read' specifies the number of files (first parameter), and number of blocks to read (second and third parameters are equal).
     
     We only observe 'db file parallel read' in Test:
        exec db_file_read_test('parallel', 1, 333);   

Event waited on                             Times   Max. Wait  Total Waited
----------------------------------------   Waited  ----------  ------------
db file scattered read                          4        0.00          0.00
db file parallel read                           2        0.00          0.00

-- Syscalls and Raw Trace File --
readv: 'db file scattered read' ela= 49  file#=917 block#=10368 blocks=8     obj#=2260477 (Index KSUN_TAB#I1)
readv: 'db file scattered read' ela= 27  file#=917 block#=128   blocks=8     obj#=2260476 (Table KSUN_TAB) 
readv: 'db file scattered read' ela= 21  file#=917 block#=136   blocks=8     obj#=2260476
pread: 'db file parallel read'  ela= 422 files=1   blocks=127   requests=127 obj#=2260476
pread: 'db file parallel read'  ela= 334 files=1   blocks=127   requests=127 obj#=2260476
pread: 'db file scattered read' ela= 264 file#=917 block#=409   blocks=66    obj#=2260476

------------------------------ dtrace ------------------------------
  PROBEFUNC      FD  RETURN_SIZE     COUNT
  pread         260       540672         1
  lseek         260            0         2
  readv         260        65536         3
  pread         260         8192       254
  
  PROBEFUNC      FD  MAX_READ_Blocks
  readv         260                8
  pread         260               66

TOTAL_SIZE = 2818048   , TOTAL_READ_Blocks = 344   , TOTAL_READ_CNT = 258
From above SQL Raw Trace File and Dtrace, we can see: first 3 'db file scattered read' with "blocks=8", corresponding to 3 readv, each reads 65536 Bytes (8 Blocks). next 2 'db file parallel read' with "blocks=127", corresponding to 254 pread, each reads 8192 Bytes (1 Block). last 'db file scattered read' with "blocks=66", corresponding to 1 pread, which reads 540672 Bytes (66 Blocks). In other words, there exist only 3 readv, which read 3*65536 Bytes=24 Blocks. These 3 readv can neither match to 254 (=127 + 127) Blocks 'db file parallel read', nor 66 Blocks 'db file scattered read'. Therefore both 'db file parallel read' and 'db file scattered read' in this test are accomplished by pread. 'db file parallel read' is similar to 'db file sequential read', the difference is that former reads multi Blocks (probably asynchronously), but later reads one single Block. In fact, we can observe the 254 aio requests by:

SQL > exec db_file_read_test('parallel', 1, 333);

sudo dtrace -n '
syscall::pread:entry / pid == $1 && arg0 == $2 / {self->pread_fd = arg0;} 
syscall::pread:return/ pid == $1 && self->pread_fd == $2 / 
 {@STACK_CNT[probefunc, self->pread_fd, arg1, ustack(5, 0)]=count(); self->pread_fd = 0;}
' 11352 260 

  pread   260   540672
    libc.so.1`_pread+0xa
    oracle`skgfqio+0x284
    oracle`ksfd_skgfqio+0x195
    oracle`ksfd_skgfrvio+0xcb4
    oracle`ksfd_vio+0x9a3
        1
        
  pread   260   8192
    libc.so.1`_pread+0xa
    libaio.so.1`_aio_do_request+0x18e
    libc.so.1`_thr_setup+0x5b
    libc.so.1`_lwp_start
      254
'db file parallel read' has 2 "plural" parameters, P1 (files), P3 (requests) (P2=P3), each of which can denote one dimension of parallel operations. P1 (files) signifies multi files reading in parallel. P3 (requests) stands for multi parallel Disk Reading requests delegating to multi-threading LWP processes. Similar to 'log file parallel write' and 'control file parallel write', in which first parameter "files" represents number of log files (in one redo group) and number of control files. In the above example, since files=1, therefore 'db file parallel read' implies multi requests. Because multi requests are performed asynchronously, probably better named as 'db file async read'. If we set DISK_ASYNCH_IO=false (and restart DB), there is no more aio observed with dtrace as shown in following output, but SQL Trace is not able to reveal this setting change, and it still shows the same output as above.

  pread   268   540672
    libc.so.1`_pread+0xa
    oracle`skgfqio+0x284
    oracle`ksfd_skgfqio+0x195
    oracle`ksfd_skgfrvio+0xcb4
    oracle`ksfd_vio+0x9a3
        1
  pread    268   8192
    libc.so.1`_pread+0xa
    oracle`skgfqio+0x284
    oracle`ksfd_skgfqio+0x203
    oracle`ksfdgo+0x188
    oracle`ksfd_sbio+0xdd1
      254   
The last 'db file scattered read' with "blocks=66" also shows one pread can read 66 Blocks, much higher than db_file_multiblock_read_count=32 in this database. By the way, we have 3 readv, but only 2 lseek, so there are probably 2 readv share one lseek.


5. V$FILESTAT vs. V$IOSTAT_FILE Views


There exist two Oracle views recording file disk I/O statistics:
  1. V$FILESTAT and its Cumulative DBA_HIST_FILESTATXS in centisecond since Oracle 8 (or 7).
  2. V$IOSTAT_FILE and DBA_HIST_IOSTAT_* in milliseconds since Oracle 11.
For temp files, the counterpart of V$FILESTAT is V$TEMPSTAT; and in V$IOSTAT_FILE, filetype_name is marked as 'Temp File'.

According to MOS (Doc ID 2222370.1): What Does "1-bk Rds/s" and "Av 1-bk Rd(ms)" in "Tablespace IO Stats" and "File IO Stats" in AWR Report Mean? (Doc ID 2222370.1)
"Tablespace IO Stats" and "File IO Stats" in AWR report come from the columns of DBA_HIST_FILESTATXS.

But AWR basis table SYS.WRH$_IOSTAT_FILETYPE is filled by V$IOSTAT_FILE.

INSERT INTO WRH$_IOSTAT_FILETYPE (dbid,
                                  con_dbid,
                                  snap_id,
                                  instance_number,
                                  filetype_id,
                                  small_read_megabytes,
                                  small_write_megabytes,
                                  large_read_megabytes,
                                  large_write_megabytes,
                                  small_read_reqs,
                                  small_write_reqs,
                                  small_sync_read_reqs,
                                  large_read_reqs,
                                  large_write_reqs,
                                  small_read_servicetime,
                                  small_write_servicetime,
                                  small_sync_read_latency,
                                  large_read_servicetime,
                                  large_write_servicetime,
                                  retries_on_error)
    SELECT :dbid,
           :srcdbid                    con_dbid,
           :snap_id,
           :instance_number,
           filetype_id,
           SUM (small_read_megabytes)  small_read_megabytes,
           SUM (small_write_megabytes) small_write_megabytes,
           SUM (large_read_megabytes)  large_read_megabytes,
           SUM (large_write_megabytes) large_write_megabytes,
           SUM (small_read_reqs)       small_read_reqs,
           SUM (small_write_reqs)      small_write_reqs,
           SUM (small_sync_read_reqs)  small_sync_read_reqs,
           SUM (large_read_reqs)       large_read_reqs,
           SUM (large_write_reqs)      large_write_reqs,
           SUM (small_read_servicetime) small_read_servicetime,
           SUM (small_write_servicetime) small_write_servicetime,
           SUM (small_sync_read_latency) small_sync_read_latency,
           SUM (large_read_servicetime) large_read_servicetime,
           SUM (large_write_servicetime) large_write_servicetime,
           SUM (retries_on_error)      retries_on_error
      FROM v$iostat_file
  GROUP BY filetype_id;  
In AWR Report, both V$IOSTAT_FILE and V$FILESTAT data are visible in different places. For example, in Section: "IOStat by Filetype summary", the last 2 Columns are named as "Small Read" and "Large Read", which are probably from V$IOSTAT_FILE. But in Section "Tablespace IO Stats" and "File IO Stats", the Column names are prefixed by "1-bk Rd", which are from from V$FILESTAT. Therefore, potential stats inconsistence can appear even in the same AWR report.


6. DB File Read Stats Tests


Run test (see appended Test Code):

alter session set timed_statistics = true; 
alter session set statistics_level=all; 

truncate table read_stats;
exec db_file_read_test('single',    1, 333);
exec db_file_read_test('scattered', 1, 333);
exec db_file_read_test('parallel',  1, 333);
exec db_file_read_test('full',      1, 333);
and than watch statistics in both views (AIX and Linux are added for comparison. Copy HTML Table to Excel for easy reading):

select test_name,  
       phyrds, phyblkrd, singleblkrds, singleblkrdtim, 
       ceil(small_read_megabytes*1024*1024/8192) small_read_blks,       -- converted to Block for comparison 
       small_read_reqs, small_read_servicetime, small_sync_read_reqs, small_sync_read_latency,  
       ceil(large_read_megabytes*1024*1024/8192) large_read_blks,       -- converted to Block for comparison 
       large_read_reqs, large_read_servicetime 
from read_stats_delta_v where phyrds > 0 order by test_name desc, test_timestamp;

OS test_name phyrds phyblkrd singleblkrds singleblkrdtim small_read_blks small_read_reqs small_read_servicetime small_sync_read_reqs small_sync_read_latency large_read_blks large_read_reqs large_read_servicetime
Solaris single
333
641
289
10
640
333
0
289
0
0
0
0
Solaris scattered
333
2664
0
0
2560
333
0
0
0
0
0
0
Solaris parallel
258
344
254
0
384
257
0
508
0
0
1
0
Solaris full
24
341
1
0
0
17
0
1
0
256
7
0
AIX single
333
333
333
150
384
333
37
333
37
0
0
0
AIX scattered
333
333
333
1460
384
333
1310
333
1310
0
0
0
AIX parallel
260
335
259
110
256
259
17
259
17
0
1
1
AIX full
24
341
1
20
128
17
41
1
12
256
7
26
Linux single
333
641
289
0
640
333
0
289
0
0
0
0
Linux scattered
333
2664
0
0
2688
333
0
0
0
0
0
0
Linux parallel
258
344
254
0
256
257
0
508
0
0
1
0
Linux full
24
341
1
0
128
17
0
1
0
256
7
0


7. DB File Read Stats Test Discussion


The first 4 stats Columns (green) are from V$FILESTAT, the rest from V$IOSTAT_FILE. Here some observations:
(a). V$FILESTAT.phyrds matches TOTAL_READ_CNT, equal to V$IOSTAT_FILE.(small_read_reqs + large_read_reqs).
      V$FILESTAT.phyblkrd matches TOTAL_READ_Blocks, approximate to V$IOSTAT_FILE.(small_read_blks + large_read_blks).
      V$FILESTAT.singleblkrds matches V$IOSTAT_FILE.small_sync_read_reqs except "parallel".
         V$FILESTAT.singleblkrds are 1 DB Block(8192 Bytes) Read (' db file sequential read' or 'db file parallel read').
         It is not clear why small_sync_read_reqs (508) in "parallel" is much higher than dtrace TOTAL_READ_Blocks (344).
     V$IOSTAT_FILE.small_read_reqs includes small_sync_read_reqs execept "parallel".
     V$IOSTAT_FILE.small_sync_read_reqs matches 'db file sequential read' except "parallel".
     
     All time metrics are (almost) zero: singleblkrdtim, small_read_servicetime, small_sync_read_latency, large_read_servicetime.
     Time in V$FILESTAT is in centiseconds, V$IOSTAT_FILE in milliseconds. Both could have rounding errors.
     
     AIX seems able to record the time metrics, probably because they are bigger than the minimum time granule.

(b). Only 'scattered' Test:
          exec db_file_read_test('scattered', 1, 333);
     shows much higher small_read_blks: 2560 Blocks by small_read_reqs: 333 Requests (2560/333 approximately 8 Blocks per Request), 
     marked as "db file scattered read" and accomplished by "readv".

     However, in V$IOSTAT_FILE, they are entirely classified as SMALL_READ_MEGABYTES (small_read_blks) and SMALL_READ_REQS.
     But Oracle 12c V$IOSTAT_FILE Docu said that SMALL_READ_* are all about "single block read" even though dtrace evidenced 
     them as multi block read (8 Blocks per readv). 
     
     Logically it is controversial if small_read_blks is bigger than small_read_reqs because each single block read request
     can read only one single block.

(c). 'db file parallel read' in "parallel" test seems running asynchronously, and hard to collect precise statistics.

(d). V$IOSTAT_FILE has one pair of stats about single block read
        -. small_read_reqs / small_read_servicetime
        -. small_sync_read_reqs / small_sync_read_latency

(e). Oracle Time Accounting also evolves with time. MOS:
       I/O Wait Event Accounting Change in the RDBMS 12.1.0.2.0 Patch Set Release (Doc ID 1967410.1)
     said:
       Starting with the RDBMS 12.1.0.2.0 Patch Set Release, the accounting for certain I/O-related wait events has been updated 
       to be more accurate than it was before. As a result, these wait events may show an increase of wait times of around 10%.
         
       The wait events that are affected include, but are not limited to, the following:
         db file sequential read
         db file scattered read
         db file parallel read
         log file parallel write


8. Test Code



drop tablespace ksun_ts including contents and datafiles;
create tablespace ksun_ts datafile '/oratestdb/oradata/testdb/ksun_ts.dbf' size 200m online;

drop table ksun_tab;

-- DB_BLOCK_SIZE = 8192, each row occupies one BLOCK
create table ksun_tab tablespace ksun_ts as 
select level x, rpad('ABC', 3500, 'X') y, rpad('ABC', 3500, 'X') z from dual connect by level <= 1e4; 

select round(bytes/1024/1024) mb, blocks from dba_segments where segment_name = 'KSUN_TAB';       
--80 10240

create index ksun_tab#i1 on ksun_tab(x) tablespace ksun_ts;

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

drop tablespace ksun_ts_aux including contents and datafiles;
create tablespace ksun_ts_aux datafile '/oratestdb/oradata/testdb/ksun_ts_aux.dbf' size 200m online;

drop table ksun_rid;
create table ksun_rid tablespace ksun_ts_aux as select x, rowid rid from ksun_tab;
create index ksun_rid#i1 on ksun_rid(x) tablespace ksun_ts_aux;

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

create or replace view read_stats_v as
select to_char(localtimestamp, 'yyyy-mm-dd hh24:mi:ss') test_timestamp
      ,phyrds, phyblkrd, singleblkrds, 10*singleblkrdtim singleblkrdtim
      ,small_read_megabytes, small_read_reqs, small_read_servicetime, small_sync_read_reqs, small_sync_read_latency
      ,large_read_megabytes, large_read_reqs, large_read_servicetime
      ,f.name
from   v$filestat v8,  v$iostat_file v11, v$datafile f
where  v8.file#    = f.file# 
  and  v11.file_no = f.file#
  and  f.name  like '%ksun_ts.dbf';
  
drop table read_stats;

create table read_stats as select 'setall_seq_readxx' test_name, v.* from read_stats_v v where 1=2;

create or replace view read_stats_delta_v as
select test_name, test_timestamp
      ,phyrds         - lag(phyrds)         over(partition by test_name order by test_timestamp)                   phyrds                 
      ,phyblkrd       - lag(phyblkrd)       over(partition by test_name order by test_timestamp)                   phyblkrd              
      ,singleblkrds   - lag(singleblkrds)   over(partition by test_name order by test_timestamp)                   singleblkrds 
      ,singleblkrdtim - lag(singleblkrdtim) over(partition by test_name order by test_timestamp)                   singleblkrdtim         
      ,small_read_megabytes    - lag(small_read_megabytes)  over(partition by test_name order by test_timestamp)   small_read_megabytes  
      ,small_read_reqs         - lag(small_read_reqs)         over(partition by test_name order by test_timestamp) small_read_reqs 
      ,small_read_servicetime  - lag(small_read_servicetime)  over(partition by test_name order by test_timestamp) small_read_servicetime 
      ,small_sync_read_reqs    - lag(small_sync_read_reqs)    over(partition by test_name order by test_timestamp) small_sync_read_reqs 
      ,small_sync_read_latency - lag(small_sync_read_latency) over(partition by test_name order by test_timestamp) small_sync_read_latency
      ,large_read_megabytes    - lag(large_read_megabytes)    over(partition by test_name order by test_timestamp) large_read_megabytes
      ,large_read_reqs         - lag(large_read_reqs)         over(partition by test_name order by test_timestamp) large_read_reqs  
      ,large_read_servicetime  - lag(large_read_servicetime)  over(partition by test_name order by test_timestamp) large_read_servicetime 
from read_stats s;

create or replace procedure db_file_read_test (p_test_name varchar2, p_loops number, p_rows number) as
 l_max_y           varchar2(3500);
 type tab_rowid is table of rowid index by pls_integer;
 l_rowid_cache     tab_rowid;
begin
  case 
   when p_test_name = 'single' then
     select rid bulk collect into l_rowid_cache from ksun_rid where x between 1 and p_rows;                
   when p_test_name = 'scattered' then
     select rid bulk collect into l_rowid_cache from ksun_rid where mod(x, 10) = 0 and rownum <= p_rows;   
   else null;
 end case;
 dbms_output.put_line('Number of Rows to read = '|| l_rowid_cache.count);
 
 insert into read_stats select p_test_name test_name, v.* from read_stats_v v;
 for i in 1..p_loops loop
  execute immediate 'alter system flush buffer_cache';
  dbms_lock.sleep(1);
  case 
    when p_test_name = 'single' then
     for r in 1..l_rowid_cache.count loop
       -- adjacent rowid, single block read, 'db file sequential read'
       select /*+ single_read */ y into l_max_y from ksun_tab t where rowid = l_rowid_cache(r);
     end loop;
    when p_test_name = 'scattered' then
     for r in 1..l_rowid_cache.count loop
       -- jumped rowid, scattered read, 'db file scattered read'
       select /*+ scattered_read */ y into l_max_y from ksun_tab t where rowid = l_rowid_cache(r);
     end loop;   
   when p_test_name = 'parallel' then
     -- table access by index rowid batched   'db file parallel read'
     select /*+ index(t ksun_tab#i1) parallel_read */ max(y) into l_max_y from ksun_tab t where x between 1 and p_rows;
   when p_test_name = 'full' then
     -- table access by FULL
     select /*+ full_read */ max(y) into l_max_y from ksun_tab t where rownum <= p_rows;
   end case;
  dbms_lock.sleep(1);
  insert into read_stats select p_test_name test_name, v.* from read_stats_v v;
  end loop;
  commit;
end;
/


9. Dtrace Script



pfiles 11352
  260: /oratestdb/oradata/testdb/ksun_ts.dbf
  
--Dtrace Script

#!/usr/sbin/dtrace -Zs

/* 
 * read_dtrace.d   pid   fd
 * chmod u+x read_dtrace.d
 * sudo ./read_dtrace.d 11352 260 
*/

BEGIN / $1 > 0 && $2 > 0 / 
 {TOTAL_SIZE = 0; TOTAL_READ_CNT = 0; } 
syscall::pread:entry / pid == $1 && arg0 == $2 / 
  {self->pread_fd = arg0; self->pread_t = timestamp;} 
syscall::pread:return/ pid == $1 && self->pread_fd == $2 / 
 {@CNT[probefunc, self->pread_fd, arg1] = count(); 
  @MAXB[probefunc, self->pread_fd] = max(arg1/8192);
  @ETIME[probefunc, self->pread_fd] = quantize(timestamp- self->pread_t); 
   TOTAL_SIZE = TOTAL_SIZE + arg1; TOTAL_READ_CNT = TOTAL_READ_CNT + 1;
   self->pread_fd = 0;} 
syscall::readv:entry / pid == $1 && arg0 == $2 / 
 {self->readv_fd = arg0; self->readv_t = timestamp; } 
syscall::readv:return/ pid == $1 && self->readv_fd == $2 / 
 {@CNT[probefunc, self->readv_fd, arg1] = count(); 
  @MAXB[probefunc, self->readv_fd] = max(arg1/8192);
  @ETIME[probefunc, self->readv_fd] = quantize(timestamp- self->readv_t); 
   TOTAL_SIZE = TOTAL_SIZE + arg1; TOTAL_READ_CNT = TOTAL_READ_CNT + 1;
   self->readv_fd = 0;} 
syscall::kaio:entry  / pid == $1 && arg1 == $2 / 
 {self->kaio = arg1;} 
syscall::kaio:return / pid == $1 && self->kaio == $2 /  
 {@CNT[probefunc,  self->kaio, arg1] = count(); self->kaio  = 0;} 
syscall::lseek:entry / pid == $1 && arg0 == $2/ 
 {@CNT[probefunc, arg0, 0] = count(); } 
END / $1 > 0 && $2 > 0 / 
    {printf("\n%11s  %6s %12s %9s \n", "PROBEFUNC", "FD", "RETURN_SIZE", "COUNT");
   printa("  %-10s %6d %12d %9@d\n", @CNT);
   printf("\n%11s  %6s %16s \n", "PROBEFUNC", "FD", "MAX_READ_Blocks");
   printa("  %-10s %6d %16@d\n", @MAXB);
   printf("\nTOTAL_SIZE = %-10d, TOTAL_READ_Blocks = %-6d, TOTAL_READ_CNT = %-6d\n", 
             TOTAL_SIZE, TOTAL_SIZE/8192, TOTAL_READ_CNT);
   printa("\n%-10s %6d %16@d\n", @ETIME);}

Monday, January 8, 2018

Oracle Latch Misses, Spin_gets, Sleeps and Recurrent_Misses

After the correction of Blog: Is latch misses statistic gathered or deduced ? I re-examined a few hourly AWR reports in one heavily loaded system (Oracle 12.1.0.2.0).


1. Latch Statistics


We observed two distinctive Sets of Latch Statistics in AWR reports.

Here AWR Top Events Section and "Latch Sleep Breakdown" Section in both Cases.


1.1. Case-1: Recurrent Spin_gets


Top 10 Foreground Events by Total Wait Time

Event Waits Total Wait Time (sec) Avg wait (ms) % DB time Wait Class
latch: cache buffers chains 1,078,528 291.7K 270.50 37.6 Concurrency
latch: row cache objects 423,293 78.2K 184.86 10.1 Concurrency

Latch Sleep Breakdown

  • ordered by misses desc
  • Latch Name Get Requests Misses Sleeps Spin Gets
    cache buffers chains 15,134,617,472 176,939,147 1,040,507 314,665,205
    row cache objects 101,225,008 4,075,626 429,314 3,673,739
    Result Cache: RC Latch 7,876,403 44,622 25,380 19,440

    Look "cache buffers chains" (CBC) statistics:
         Misses    = 176,939,147
         Sleeps    =   1,040,507
         Spin Gets = 314,665,205
    
    Spin_Gets(314,665,205) > Misses(176,939,147) attested the existence of recurrent Spin_Gets.


    1.2. Case-2: Recurrent Sleeps


    Top 10 Foreground Events by Total Wait Time

    Event Waits Total Wait Time (sec) Avg wait (ms) % DB time Wait Class
    latch: cache buffers chains 1,630,692 748.3K 458.90 92.7 Concurrency
    latch: row cache objects 314,199 23.9K 76.14 3.0 Concurrency

    Latch Sleep Breakdown

  • ordered by misses desc
  • Latch Name Get Requests Misses Sleeps Spin Gets
    row cache objects 55,852,812 3,505,131 320,033 3,224,234
    cache buffers chains 1,722,527,053 2,564,938 2,819,003 1,026,077
    kokc descriptor allocation latch 5,984,155 343,396 17,985 326,266

    Look "cache buffers chains" (CBC) statistics:
         Misses    = 2,564,938
         Sleeps    = 2,819,003
         Spin Gets = 1,026,077
    
    Sleeps(2,819,003) > Misses(2,564,938) evidenced the existence of recurrent Sleeps.


    2. Recurrent_Misses


    Every unsuccessful Spin_Get will trigger a Sleep (or each Sleep follows a Spin_Get), every unsuccessful Sleep will result in a successive longer Sleep.

    Latch Sleep is started with an initial duration of 10ms (or 1ms), and progressively elevated after every unsuccessful Sleep (See later discussion of V$LATCH.sleep[1-11], V$EVENT_HISTOGRAM.wait_time_milli, and pseudo-code in Book: Oracle Performance Firefighting Page 79).

    Suppose initial duration being 10ms, Avg wait (ms) = 270.50 (Case-1) indicates that there exist Sleeps, which are not initial Sleeps, hence recurrent Sleeps.

    For OS scheduler Time Slice of 10ms, Avg wait (ms) = 270.50 signifies that majority of Sleeps spans more than 20 Ticks, probably recurrent Sleeps (also possible not awakened from waitqueue, i.e., not scheduled into runqueue).

    Recurrent_Misses can be computed by:
    Case-1:  
        spin_gets + sleeps - misses = 314,665,205 + 1,040,507 - 176,939,147 = 138,766,565 > 0
    
    Case-2:
        spin_gets + sleeps - misses = 1,026,077 + 2,819,003 - 2,564,938 = 1,280,142 > 0
    
    Therefore, above formula could be refined as:
           sleeps + spin_gets – misses 
         = recurrent_misses 
         = recurrent_sleeps + recurrent_spin_gets 
    
    Look proportion of recurrent Misses:
        Case-1: recurrent_misses / Misses amounts to 78.4% (138,766,565 / 176,939,147)
        Case-2: recurrent_misses / Misses amounts to 49.9% (1,280,142 / 2,564,938)
    
    both are serious cases of latch misses.


    3. Observations

    (1). Latch Sleep Breakdown Table shows:
             Case-1: "cache buffers chains": Spin_Gets(314,665,205) > Misses(176,939,147)
             Case-2: "cache buffers chains": Sleeps(2,819,003)           > Misses(2,564,938)
       
       Hence there exist Spin_Get and Sleep which are not followed by one success Get, that is, recurrent Miss.
       Or we can say that each Miss can trigger multi Spin_Gets or Sleeps.
       
    (2). Sleeps can be bigger than Spin_Gets, for example, 
          Case-1: "Result Cache: RC Latch": Sleeps(25,380) > Spin_Gets(19,440)
       
    (3). Usually when one Latch has very slight contention, 
           misses = sleeps + spin_gets
       It means that each Miss is resolved either by one Spin_Get, or by one Sleep.
       This also indicates that immediately after each Sleep (OS Process waking up and rescheduled), Oracle makes one Latch Get.
       
    (4). Top 10 Foreground Events Table shows:
          Latch Sleeps is approximate to Event Waits, i.e V$LATCH.sleeps = V$SYSTEM_EVENT.total_waits.
       It signifies that only Latch Sleeps is counted as Waits because Process is on the wait queue. 
       Latch Spin_Gets is not counted because Process is on CPU (runqueue).
    
    By the above observations, it is not clear how Oracle makes the counting of Misses, Sleeps and Spin_Gets in willing-to-wait mode. And it is hard to reconstruct a pseudo-code to match above countings.


    4. Discussions

    (1). Oracle session's Response Time is made of Service Time and Queue Time. Spin_Gets is counted as Service Time since it is on CPU.
       Whereas Sleeps is categorized as Queue Time since it is on waiting. For details, see Book: 
       Oracle Performance Firefighting Page 74-91.
    
    (2). As discussed, generally, Latch Spins is burning CPU; whereas Latch Sleeps yields CPU. Therefore when talking about Latch contentions, 
       it would be necessary to distinguish between Spin_gets and Sleeps. 
       As tested, usually Spin_gets of Latch Misses are caused by frequently concurrent access; whereas Sleeps of Latch Misses are triggered 
       by Invalidations or DML Modifications. 
       
    (3). Oracle V$LATCH SLEEP evaluated, which leaks the algorithm changing.
     
       In Oracle 9i, V$LATCH is documented as:
         SLEEP[1|2|3]           Waits that slept 1 time through 3 times, respectively
         SLEEP4                   Waits that slept 4 or more times
         SLEEP[5|6|7|8|9|10|11] present for compatibility with previous releases of Oracle. No data is accumulated.
         
       Since Oracle 10g, V$LATCH is updated as:
         SLEEP[1|2|3|4|5|6|7|8|9|10|11] deprecated. As a substitute, query V$EVENT_HISTOGRAM for EVENT like latch free or latch:%.                                 
       
       So the original design elevated SLEEP in 11 grades, with 9i, it was reduced to 4, and 10g regulated it in a new approach. 
       
    (4). Suppose OS scheduler Time Slice is 10ms (system clock tick frequency of 100 hertz), 
       Wait Event "Avg wait (ms)"/10 represents the paused ticks before placed back into the runqueue.
       
    (5). There are two sorts of Latches, one has children (see V$LATCH_CHILDREN), for example, "cache buffers chains";
       other one has no children and hence an instance_wide single Latch, for example, "Result Cache: RC Latch".
       Therefore, when comparing Sleeps and Spin_Gets among Latches, the number of children should be taken into account.
       For example, single Latch can serialize whole system.
    
    With following query, we can see number of Blocks protected per CBC Child Latch:
    
    with block_size as (select value b_val from v$parameter where name in ('db_block_size'))
        ,cache_size as (select sum(value) c_val from v$parameter where name in ('db_cache_size', 'db_keep_cache_size', 'db_recycle_cache_size'))
        ,latch_cnt  as (select count(*) cnt from v$latch_children where name in ('cache buffers chains'))
    select b_val, round(c_val/1024/1024) cache_size_mb, l.cnt, ceil(c.c_val/b_val/cnt) block_per_latch
    from block_size b, cache_size c, latch_cnt l;
    

    5. Queries

    -- v$latch
    select name, misses, sleeps, spin_gets
          ,sign((sleeps + spin_gets) - misses) miss_deduced_sign
          ,((sleeps + spin_gets) - misses)     recurrent_misses
          ,trunc((abs((sleeps + spin_gets) - misses) / nullif(misses, 0)) * 100 ,2)*100 recurrent_misses_perc
    from v$latch where misses > 1000
    order by misses desc;
    
    -- v$system_event
    select event, total_waits, round(time_waited_micro/1e6, 2) time_waited_sec, average_wait*1e4 average_wait_micro, e.*
    from v$system_event  e
    where event in ('latch free', 'latch: shared pool', 'latch: row cache objects', 'latch: cache buffers chains')
    order by e.event;
    
    -- v$session_event
    select sid, event, total_waits, round(time_waited_micro/1e6, 2) time_waited_sec, average_wait*1e4 average_wait_micro, e.*
    from v$session_event e
    where e.event in ('latch free', 'latch: shared pool', 'latch: row cache objects', 'latch: cache buffers chains')
     --and sid in (217, 1217)
    order by e.event, e.sid;
    
    -- dba_hist_latch (One can also query dba_hist_latch_parent, dba_hist_latch_children)
    select snap_id End_Snap, end_interval_time Snap_Time, latch_name Latch_Name
          ,gets_d       Get_request
          ,misses_d     Misses
          ,sleeps_d     Sleeps
          ,spin_gets_d  Spin_Gets
          --,v.*
    from (
    select gets-lag(gets) over(partition by latch_name order by e.snap_id) gets_d
          ,misses-lag(misses) over(partition by latch_name order by e.snap_id) misses_d
          ,sleeps-lag(sleeps) over(partition by latch_name order by e.snap_id) sleeps_d
          ,spin_gets-lag(spin_gets) over(partition by latch_name order by e.snap_id) spin_gets_d
          ,h.end_interval_time, e.* 
    from dba_hist_latch e, dba_hist_snapshot h
    where e.snap_id = h.snap_id 
      and e.latch_name in ('Result Cache: RC Latch', 'cache buffers chains', 'row cache objects')
      order by e.snap_id desc
    ) v
    --where snap_id = 1217
    order by snap_id desc, latch_name, gets_d desc;
    
    -- dba_hist_system_event. Similar to AWR "Top 10 Foreground Events by Total Wait Time"
    select snap_id End_Snap, end_interval_time Snap_Time,   event_name Event
          ,total_waits_d                                               Waits
          ,round(time_waited_micro_d/1e6,2)                            Total_Wait_Time_sec
          ,round(time_waited_micro_d/1e3/(nullif(total_waits_d, 0)),2) Wait_Avg_ms
          --,v.*
    from (
    select total_waits-lag(total_waits) over(partition by event_name order by e.snap_id) total_waits_d
          ,total_timeouts-lag(total_timeouts) over(partition by event_name order by e.snap_id) total_timeouts_d
          ,time_waited_micro-lag(time_waited_micro) over(partition by event_name order by e.snap_id) time_waited_micro_d
          ,h.end_interval_time, e.* 
    from dba_hist_system_event e, dba_hist_snapshot h
    where e.snap_id = h.snap_id 
      and e.event_name in ('latch free', 'latch: shared pool', 'latch: row cache objects', 'latch: cache buffers chains')
      order by e.snap_id desc
    ) v
    where snap_id = 1217;
    order by snap_id desc, total_waits_d desc;
    

    Tuesday, October 31, 2017

    ORA-04025 library object lock on DATABASE Truncate Triggers

    In this Blog, we will discuss ORA-04025 Errors caused by DATABASE Truncate Triggers.

    Note 1. All tests are done in Oracle 12.1.0.2.0 on AIX, Solaris, Linux.
    Note 2. ORA-04025 Maximum Allowed Library Object Lock Allocated is also discussed in Blog:
                 ORA-04025 library object lock on AQ queue and SQL cursor


    1. DATABASE Truncate Triggers


    In Oracle 12c, there exist two DATABASE Triggers:
        XDB_PI_TRIG
        AW_TRUNC_TRG
    
    They can be discovered by:
    
    create or replace function getlong(p_trigger_name varchar2) return varchar2 as 
      l_data long; 
    begin 
      select trigger_body into l_data from dba_triggers where trigger_name = p_trigger_name; 
      return substr(l_data, 1, 4000);
    end; 
    /
    
    column owner format            a6
    column trigger_name format     a12
    column trigger_type format     a12
    column triggering_event format a15
    column status format           a8
    column description format      a40
    set wrap off
    
    SQL> select owner, trigger_name, trigger_type, triggering_event, status, description 
             --,getlong(trigger_name) trigger_body
         from dba_triggers where triggering_event like '%TRUNCATE%';
    
      OWNER  TRIGGER_NAME TRIGGER_TYPE TRIGGERING_EVEN STATUS   DESCRIPTION
      ------ ------------ ------------ --------------- -------- ----------------------------------------
      SYS    XDB_PI_TRIG  BEFORE EVENT DROP OR TRUNCAT ENABLED  sys.xdb_pi_trig
      SYS    AW_TRUNC_TRG AFTER EVENT  TRUNCATE        ENABLED  aw_trunc_trg AFTER TRUNCATE ON DATABASE
    
    
    set wrap on
    
    SQL> select getlong(trigger_name) trigger_body from dba_triggers where trigger_name = 'XDB_PI_TRIG';
    
    BEGIN
      BEGIN
        IF (sys.is_vpd_enabled(sys.dictionary_obj_owner, sys.dictionary_obj_name, xdb.DBMS_XDBZ.IS_ENABLED_CONTENTS)) THEN
          xdb.XDB_PITRIG_PKG.pitrig_truncate(sys.dictionary_obj_owner, sys.dictionary_obj_name);
        ELSIF (sys.is_vpd_enabled(sys.dictionary_obj_owner, sys.dictionary_obj_name, xdb.DBMS_XDBZ.IS_ENABLED_RESMETADATA)) THEN
          xdb.XDB_PITRIG_PKG.pitrig_dropmetadata(sys.dictionary_obj_owner, sys.dictionary_obj_name);
        END IF;
      EXCEPTION
        WHEN OTHERS THEN
         null;
      END;
    END;
    
    SQL> select getlong(trigger_name) trigger_body from dba_triggers where trigger_name = 'AW_TRUNC_TRG';
    
    BEGIN
      aw_trunc_proc(ora_dict_obj_type, ora_dict_obj_name, ora_dict_obj_owner);
    END;
    
    
    Note: ora_dict_obj_owner, ora_dict_obj_name, ora_dict_obj_type are published Attributes (public synonyms) of System Events.
          (replacing functions of earlier releases: sys.dictionary_obj_owner, sys.dictionary_obj_name, sys.dictionary_obj_type 
           defined in dbms_standard).
    
    


    2. Test


    Run following test (see appended Test Code):
    
    alter system set session_cached_cursors=2000 scope=spfile;
    alter system set processes=1100 scope=spfile;
    alter system set job_queue_processes=1000 scope=spfile;
    alter system set "_session_cached_instantiations"=1001 scope=spfile;
    
    -- restart DB
    SQL> startup force
    
    -- start Test by launching 66 Jobs (66 > floor(65535/1000)=65)
    SQL> exec gtt_truncate_job(66, 1e9);
    
    --after Test, clean jobs
    -- exec clean_jobs;
    
    During test, monitor v$db_object_cache.LOCKS for both triggers, we can see their increasing:
    
    column name format a80
    set wrap off
    
    SQL> select hash_value, locks, pins, locked_total, pinned_total, name  --v.*
           from v$db_object_cache v
          where locks > 1000
            and (hash_value in (2219505151, 679830350) 
             or (name like '%xdb.DBMS_XDBZ.%' or name like '%aw_trunc_proc%')); 
    
      HASH_VALUE  LOCKS  PINS LOCKED_TOTAL PINNED_TOTAL NAME
      ---------- ------ ----- ------------ ------------ --------------------------------------------------------------------------------
      2219505151  64141     0      1164253      1164234 BEGIN   BEGIN     IF (sys.is_vpd_enabled(sys.dictionary_obj_owner, sys.dictionar
      2219505151  64238     0      1164365            1 BEGIN   BEGIN     IF (sys.is_vpd_enabled(sys.dictionary_obj_owner, sys.dictionar
       679830350  64506     0      1164192      1164167 BEGIN   aw_trunc_proc(ora_dict_obj_type, ora_dict_obj_name, ora_dict_obj_owner);
       679830350  64602     0      1164289            1 BEGIN   aw_trunc_proc(ora_dict_obj_type, ora_dict_obj_name, ora_dict_obj_owner);
    
    After about 10 minutes, LOCKS increased closing to 65535 (66 sessions, each of which allocated about 1000 LOCKS).

    database alert.log shows ORA-04025 Errors caused by two triggers:
    
    ORA-04025: maximum allowed library object lock allocated for BEGIN
      aw_trunc_proc(ora_dict_obj_type, ora_dict_obj_name, ora_dict_obj_owner);
    END;
    
    ORA-04025: maximum allowed library object lock allocated for BEGIN
      BEGIN
        IF (sys.is_vpd_enabled(sys.dictionary_obj_owner, sys.dictionary_obj_name, xdb.DBMS_XDBZ.IS_ENABLED_CONTENTS)) THEN
          xdb.XDB_PITRIG_PKG.pitrig_truncate(sys.dictionary_obj_owner, sys.dictionary_obj_name);
        ELSIF (sys.is_vpd_enabled(sys.dictionary_obj_owner, sys.dictionary_obj_name, xdb.DBMS_XDBZ.IS_ENABLED_RESMETADATA)) THEN
          xdb.XDB_PITRIG_PKG.pitrig_dropmetadata(sys.dictionary_obj_owner, sys.dictionary_obj_name);
        END IF;
      EXCEPTION
        WHEN OTHERS THEN
         null;
      END;
    END;
    
    Both cursors can also be found by:
    
    column sql_text format a100
    set wrap off
    
    SQL > select sql_id, hash_value, executions, parse_calls, sql_text from v$sqlarea v 
          where sql_id in ('ct6c4h224pxgz', 'gx87sann8asuf') or hash_value in (2219505151, 679830350);
    
      SQL_ID        HASH_VALUE EXECUTIONS PARSE_CALLS SQL_TEXT
      ------------- ---------- ---------- ----------- --------------------------------------------------
      ct6c4h224pxgz 2219505151   13537973    13623969 BEGIN   BEGIN     IF (sys.is_vpd_enabled(sys.dicti
      gx87sann8asuf  679830350   13649026    13603140 BEGIN   aw_trunc_proc(ora_dict_obj_type, ora_dict_
    
    Blog: ORA-04025 library object lock on AQ queue and SQL cursor showed that Maximum Allowed Library Object Lock is hard limited to 65535. That is why at least 66 Jobs (66 > floor(65535/1000)=65) have to be launched to provoke ORA-04025 Error.

    Note that in the above test, we set
      session_cached_cursors=2000
    
    because of two DATABASE Truncate Triggers, each of both can acquire about 1000 LOCKs.

    As a justification, even we start one single session, LOCK_CNT is around 1000.
    
    SQL > exec clean_jobs;
     
    SQL > exec gtt_truncate_job(1, 1e9);
    
    SQL > with sq as
            (select /*+ materialize */ sid, program, saddr, event
                ,to_char(p1, 'xxxxxxxxxxxxxxx') p1, p1text
                ,to_char(p2, 'xxxxxxxxxxxxxxx') p2, p2text
                ,to_char(p3, 'xxxxxxxxxxxxxxx') p3, p3text
             from v$session
            where program like '%(J%')
          select o.kglnahsh, s.sid, holding_user_session, object_handle, l.type, mode_held, mode_requested,
                 count(*) lock_cnt     -- it goes up to about 1000
            from v$libcache_locks l, x$kglob o, sq s
           where 1= 1
             and o.kglnahsh in (2219505151, 679830350)     
             and l.object_handle = o.kglhdadr
             and l.holding_user_session = s.saddr
          group by o.kglnahsh, s.sid, holding_user_session, object_handle, l.type, mode_held, mode_requested
          order by lock_cnt desc, s.sid, holding_user_session, l.type, mode_held, mode_requested;
      
        KGLNAHSH  SID HOLDING_USER_SES OBJECT_HANDLE    TYPE  MODE_HELD MODE_REQUESTED   LOCK_CNT
      ---------- ---- ---------------- ---------------- ---- ---------- -------------- ----------
      2219505151  152 070001010D31BD08 07000100EDC018C8 LOCK          1              0       1012
      2219505151  152 070001010D31BD08 07000100ED2F4110 LOCK          1              0       1011
       679830350  152 070001010D31BD08 07000100ECDBA460 LOCK          1              0       1009
       679830350  152 070001010D31BD08 07000100ECDBBF28 LOCK          1              0       1009
    


    3. "library cache: mutex X" and "cursor: pin X"


    During test, heavy "library cache: mutex X" and "cursor: pin X" are observed on GTT truncate statement:
    
    truncate table my_gtt
     
    SQL > select mutex_identifier, mutex_type, count(*) cnt from v$mutex_sleep_history 
          group by mutex_identifier, mutex_type 
          order by cnt desc;
    
      MUTEX_IDENTIFIER MUTEX_TYPE      CNT
      ---------------- -------------- ----
             167539573 Library Cache   278
             167539573 Cursor Pin       81
       
    SQL > select hash_value, locks, pins, locked_total, pinned_total, invalidations, name 
          from v$db_object_cache where hash_value in (167539573);
    
      HASH_VALUE  LOCKS  PINS LOCKED_TOTAL PINNED_TOTAL INVALIDATIONS NAME
      ---------- ------ ----- ------------ ------------ ------------- ---------------------
       167539573      1     0        14295        26690         13002 truncate table my_gtt
       167539573      2     0        14309        26510         13026 truncate table my_gtt
       167539573      1     0        14134        26218         12874 truncate table my_gtt
       ......
     
    SQL > select sql_id, hash_value, executions, parse_calls, invalidations, sql_text 
          from v$sqlarea v where sql_id in ('1jh104s4zswvp') or hash_value in (167539573); 
     
      SQL_ID        HASH_VALUE EXECUTIONS PARSE_CALLS INVALIDATIONS SQL_TEXT
      ------------- ---------- ---------- ----------- ------------- ---------------------
      1jh104s4zswvp  167539573         86          90       1325129 truncate table my_gtt
    
    It seems that "library cache: mutex X" is caused by dynamic SQL statement "EXECUTE IMMEDIATE" because GTT truncate is executed by:
        execute immediate 'truncate table my_gtt'
    
    and both above DATABASE Truncate Triggers are probably also called by "EXECUTE IMMEDIATE" since v$db_object_cache.NAME and v$sqlarea.SQL_TEXT are formatted in lower and upper mixed cases.

    See Blogs:
        Divide and conquer the true mutex contention
        "library cache: mutex X" and Application Context
    for more discussions.


    4. XDB_PI_TRIG Trigger Components and Cost


    Run GTT truncate with SQL Trace 10046:
    
    alter trigger sys.AW_TRUNC_TRG disable;
    alter trigger sys.XDB_PI_TRIG enable;
    
    SQL > alter system flush shared_pool; 
    SQL > alter session set events '10046 trace name context forever, level 12';
    SQL > exec gtt_truncate(1);
    SQL > alter session set events '10046 trace name context off';
    
    We can see all the TOP statements:
    
    --SQL_ID: 86kwhy1f0bttn 
    SELECT /*+ ALL_ROWS */
          COUNT (*)
      FROM DBA_POLICIES V
     WHERE     V.OBJECT_OWNER = :B3
           AND V.OBJECT_NAME = :B2
           AND (V.POLICY_NAME LIKE '%xdbrls%' OR V.POLICY_NAME LIKE '%$xd_%')
           AND V.FUNCTION = :B1
           
    --SQL_ID: 1jh104s4zswvp 
    truncate table my_gtt
           
    --SQL_ID: 1u4r80k42x8jj 
    select count(FA#) from SYS_FBA_TRACKEDTABLES where OBJ# = 1001511 and bitand(FLAGS, 128)=0
    
    --SQL_ID: au7qd3c3yuk1u 
    LOCK TABLE "MY_GTT" IN EXCLUSIVE MODE WAIT 5
    
    --SQL_ID: 9ff116syaf279 
    SELECT TO_NUMBER(XS_SYS_CONTEXT('XS$SESSION', 'SESSION_XS_USER_GUID')) FROM DUAL
    
    --SQL_ID: au7qd3c3yuk1u 
    LOCK TABLE "MY_GTT" IN EXCLUSIVE MODE WAIT 5
    
    In fact, our GTT table: my_gtt is irrelevant to XDB:
    
    SELECT /*+ ALL_ROWS */
          *
      FROM DBA_POLICIES V
     WHERE     V.OBJECT_OWNER = 'XDB'
         --AND V.OBJECT_NAME = 'MY_GTT'
           AND (V.POLICY_NAME LIKE '%xdbrls%' OR V.POLICY_NAME LIKE '%$xd_%')
           AND V.FUNCTION in ('CHECKPRIVRLS_SELECTPF', 'CHECKPRIVRLS_SELECTPROPF');         
    


    5. Query against SYS_FBA_TRACKEDTABLES


    We also noticed one query on SYS_FBA_TRACKEDTABLES having TOP Executions and Parse Calls. Both number are very close, which means that each Execution requires a Parse Call.
    
    SQL > select sql_id, hash_value, executions, parse_calls, sql_text from v$sqlarea v 
    where sql_id in ('1u4r80k42x8jj') or hash_value in (2284757553);
    
      SQL_ID        HASH_VALUE EXECUTIONS PARSE_CALLS SQL_TEXT
      ------------- ---------- ---------- ----------- -------------------------------------------------------------------------------------------
      1u4r80k42x8jj 2284757553   35663414    35536924 select count(FA#) from SYS_FBA_TRACKEDTABLES where OBJ# = 1001511 and bitand(FLAGS, 128)=0
    
    -- where 1001511 is MY_GT
    
    SQL > select object_name, object_id, object_type from dba_objects where object_id = 1001511;
    
      OBJECT_NAME   OBJECT_ID  OBJECT_TYPE
      -----------  ---------- ------------
      MY_GTT          1001511        TABLE
    
    Searching in Oracle MOS, it looks like:
         Bug 15931756 - ORA-4031 / Queries against SYS_FBA_TRACKEDTABLES not shared (do not use binds) (Doc ID 15931756.8)


    6. Disable/Drop Triggers


    Trigger XDB_PI_TRIG is applied for Oracle XML Database (XDB), and AW_TRUNC_TRG for OLAP Analytic Workspaces(AW). If they are not installed/used, one can disable/drop them. These can be checked by:
    
    select comp_name, version, status from dba_registry order by 1;
    select * from v$option where parameter = 'OLAP';  
    select * from dba_feature_usage_statistics where name like '%XDB%' or name like '%OLAP%';
    select * from dba_aws;
    
    (Note: XDB_PI_TRIG trigger moved from schema XDB to SYS with PSU 11.1.0.7.16)  
    
    It is not clear why AW_TRUNC_TRG got installed although we do not buy/pay the license. Besides, the table name MY_GTT is not like 'AW$_%'.


    7. Boundary Value and Cursor Leak ?


    Set session_cached_cursors to Boundary Value: 65535:
    
    alter system set session_cached_cursors=65535 scope=spfile;
    alter system set "_session_cached_instantiations"=1001 scope=spfile;
    alter system set open_cursors=400 scope=spfile;
    alter system set cursor_sharing=exact scope=spfile;
    
    -- enable only one single truncate trigger
    alter trigger sys.AW_TRUNC_TRG disable;  
    alter trigger sys.XDB_PI_TRIG  enable;
    
    -- Restart DB
    --SQL> startup force
    
    Open one Sqlplus session and run (it takes about 10 minutes):
    
    SQL (567) > exec gtt_truncate(65535);
    
    Immediately after its finished, run following queries about Library Cache and Cursor, and look the output:
    
    set lines 400
    column name format a50
    set wrap off
    
    select hash_value, locks, pins, locked_total, pinned_total, executions, child_latch, name
      from v$db_object_cache v
     where (hash_value in (2219505151) or (name like 'BEGIN%xdb.DBMS_XDBZ.%')) 
       and locks > 100; 
       
      HASH_VALUE  LOCKS  PINS LOCKED_TOTAL PINNED_TOTAL EXECUTIONS CHILD_LATCH NAME
      ---------- ------ ----- ------------ ------------ ---------- ----------- --------------------------------------------------
      2219505151  65532     0       131070       131072      65535           0 BEGIN   BEGIN     IF (sys.is_vpd_enabled(sys.dicti
      2219505151  65533     0       131073            1      65535       62975 BEGIN   BEGIN     IF (sys.is_vpd_enabled(sys.dicti
    
    
    select hash_value, sql_id, child_number, users_opening, executions, parse_calls, invalidations
    from  v$sql v
    where (sql_id in ('ct6c4h224pxgz') or hash_value in (2219505151))
      and users_opening > 100;   
      
      HASH_VALUE SQL_ID        CHILD_NUMBER USERS_OPENING EXECUTIONS PARSE_CALLS INVALIDATIONS
      ---------- ------------- ------------ ------------- ---------- ----------- -------------
      2219505151 ct6c4h224pxgz            1         65531      65535       65535             1  
    
    
    select count(*) from v$open_cursor
    where (sql_id in ('ct6c4h224pxgz') or hash_value in (2219505151))
      and sid in (567);
      
       COUNT(*)
      ---------
          65530 
        
    select * from v$open_cursor
    where (sql_id in ('ct6c4h224pxgz') or hash_value in (2219505151))
      and sid in (567) 
      and rownum <= 3;  
    
       SID HASH_VALUE SQL_ID        SQL_TEXT                              LAST_SQL SQL_EXEC CURSOR_TYPE                     CHILD_ADDRESS   
      ---- ---------- ------------- ------------------------------------- -------- -------- ------------------------------- ----------------
       567 2219505151 ct6c4h224pxgz BEGIN   BEGIN     IF (sys.is_vpd_enab                   DICTIONARY LOOKUP CURSOR CACHED 000000017636BA68
       567 2219505151 ct6c4h224pxgz BEGIN   BEGIN     IF (sys.is_vpd_enab                   DICTIONARY LOOKUP CURSOR CACHED 000000017636BA68
       567 2219505151 ct6c4h224pxgz BEGIN   BEGIN     IF (sys.is_vpd_enab                   DICTIONARY LOOKUP CURSOR CACHED 000000017636BA68
    
    
    select s.program, s.sid, n.name p_name, t.value
    from v$session s, v$sesstat t, v$statname n 
    where s.sid=t.sid and n.statistic# = t.statistic# 
      and name = 'session cursor cache count'
      and s.sid in (567);
      
      PROGRAM       SID  P_NAME                      VALUE
      ------------ ---- --------------------------- ------
      sqlplus.exe   567  session cursor cache count  65535
      
          
    select sql_id, child_number from v$sql_shared_cursor
    where sql_id in ('ct6c4h224pxgz');
    
      SQL_ID        CHILD_NUMBER
      ------------- ------------
      ct6c4h224pxgz            0
      ct6c4h224pxgz            1      
    
    The above result shows:
      1. v$db_object_cache.LOCKS is close to 65535
      2. v$sql.USERS_OPENING is close to 65535
      3. v$open_cursor count is close to 65535
      4. 'session cursor cache count' is 65535
    
    Contrary to Blog: ORA-04025 library object lock on AQ queue and SQL cursor
      1. SESSION_CACHED_CURSORS (65535) sets hard limit of LIBRARY OBJECT LOCK, 
         instead of "_session_cached_instantiations".
      2. OPEN_CURSORS has no effect. 
    
    v$open_cursor shows that one single session for one single cursor has 65535 CURSORS (opened and parsed, or cached), all marked with CURSOR_TYPE: "DICTIONARY LOOKUP CURSOR CACHED".
    65535 is clearly over open_cursors=400, and according to Oracle Doc:
        OPEN_CURSORS specifies the maximum number of open cursors (handles to private SQL areas) a session can have at once.
    It is not clear if this a Cursor Leak.

    (MOS: Bug 25477055: INCORRECT CURSOR_TYPE IN V$OPEN_CURSOR FOR SESSION CACHED CURSOR said:
        "DICTIONARY LOOKUP CURSOR CACHED" is misreported, it should be "SESSION CURSOR CACHED").

    Immediately after above gtt_truncate(65535) terminated, run following statement in another session, it will raise ORA-04025:
    
    SQL (1124)> exec gtt_truncate(100);
    
      ORA-04025: maximum allowed library object lock allocated for BEGIN
      BEGIN
      IF (sys.is_vpd_enabled(sys.dictionary_obj_owner, sys.dictionary_obj_name, xdb.DBMS_XDBZ.IS_ENABLED_CONTENTS)) THEN
      xdb.XDB_PITRIG_PKG.pitrig_truncate(sys.dictionary_obj_owner, sys.dictionary_obj_name);
      ELSIF (sys.is_vpd_enabled(sys.dictionary_obj_owner, sys.dictionary_obj_name, xdb.DBMS_XDBZ.IS_ENABLED_RESMETADATA)) THEN
      xdb.XDB_PITRIG_PKG.pitrig_dropmetadata(sys.dictionary_obj_owner, sys.dictionary_obj_name);
      END IF;
      EXCEPTION
      WHEN OTHERS THEN
      null;
      END;
      END;  
    


    8. Simple Truncate Trigger Reproducing


    We will use a (almost) empty Truncate Trigger to demonstrate that ORA-04025 is caused by the existence of Truncate Trigger, but irrelevant to its substances.

    Disable (or Drop) both Oracle Truncate Triggers:
    
    alter trigger sys.AW_TRUNC_TRG disable;
    alter trigger sys.XDB_PI_TRIG  disable;
    
    Create a new trivial Trigger:
    
    create or replace trigger SYS.KSUN_TEST_TRIG
    before drop or truncate ON database 
      BEGIN
        IF 1=2 THEN
          null;
        END IF;
      END;
    /
    
    Configure session_cached_cursors to maximum value:
    
    alter system set session_cached_cursors=65535 scope=spfile;
    alter system set "_session_cached_instantiations"=1001 scope=spfile;
    alter system set open_cursors=400 scope=spfile;
    alter system set cursor_sharing=exact scope=spfile;
    
    Run test:
    
    SQL> exec gtt_truncate_job(2, 1e9);
    
    Monitor v$db_object_cache.LOCKS:
    
    column name format a80
    set wrap off
    
    SQL> select hash_value, locks, pins, locked_total, pinned_total, executions, name  --v.*
           from v$db_object_cache v
          where locks > 1000
            and (name like '%1=2%' or name like '%KSUN_TEST_TRIG%');
            
      HASH_VALUE  LOCKS  PINS LOCKED_TOTAL PINNED_TOTAL EXECUTIONS NAME
      ---------- ------ ----- ------------ ------------ ---------- -----------------------------------------------------
      2564248212  65514     0        65516        65517      65516 BEGIN     IF 1=2 THEN       null;     END IF;   END;
      2564248212  65514     0        65516            1      65515 BEGIN     IF 1=2 THEN       null;     END IF;   END;        
    
    and alert.log shows:
            
    ORA-04025: maximum allowed library object lock allocated for BEGIN
        IF 1=2 THEN
          null;
        END IF;
      END;
    

    9. Hidden Parameter: _system_trig_enabled


    Oracle provided a hidden Parameter:
       _system_trig_enabled   are system triggers enabled   default: TRUE
    
    to disable system triggers.

    If it is deactivated by:
    
      alter system set "_system_trig_enabled"=false;  
    
    query below returns no more both trigger rows:
    
     select hash_value, locks, pins, locked_total, pinned_total, name  --v.*
           from v$db_object_cache v
          where locks > 0
            and (hash_value in (2219505151, 679830350) 
             or (name like '%xdb.DBMS_XDBZ.%' or name like '%aw_trunc_proc%'))
    
    Both triggers are listed in following sql:
    
    select a.obj#, a.sys_evts, b.name, a.*, b.*
      from sys.trigger$ a, sys.obj$ b
     where a.sys_evts > 0 and a.obj#=b.obj#
       and name in ('XDB_PI_TRIG', 'AW_TRUNC_TRG');
    
    Hence they are effectively disabled with "_SYSTEM_TRIG_ENABLED=FALSE".
    (see MOS Doc ID 244524.1: How to find triggers being disabled with _SYSTEM_TRIG_ENABLED parameter)


    10. Test Code


    
    create global temporary table my_gtt (id number) on commit preserve rows;
    
    create or replace procedure gtt_truncate(p_cnt number) is
    begin
     for i in 1..p_cnt loop
        execute immediate 'truncate table my_gtt';
        --dbms_lock.sleep(0.01);
      end loop;
    end;
    /
    
    create or replace procedure gtt_truncate_job(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 gtt_truncate('||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;
      
      -- select * from dba_jobs;
      -- select * from dba_jobs_running;
    end;
    /
    

    Wednesday, October 4, 2017

    Result Cache:0 rows updated Invalidations (IV)

    We have hit a new case of Result Cache Invalidations, in which result_cache Table has "0 rows updated". If we update another non result_cache Table in the same transaction, Result Cache becomes Invalid.

    At the end of this Blog, we also give a Workaround.

    This is the 4th case of Result Cache Invalidation, which can cause Result Cache: RC Latch Contention.
    1. PL/SQL Function Result Cache Invalidation (I) (DML and Select for Update)
    2. Result Cache: RC Latch Contention and RESULT_CACHE_MAX_SIZE (II))
    3. Result Cache: RC Latch Contention and PL/SQL Exception Handler (III)
    4. Result Cache:0 rows updated Invalidations (IV) 
    
    Note: All tests are done in Oracle 11.2.0.4, 12.1.0.2, 12.2.0.1 on AIX, Solaris, Linux with 6 physical processors.

    Update (25-Jan-2018): The reported issue accepted as Bug and public visible.
        Bug 26964029 : RESULT CACHE INVALIDATED EVEN THOUGH UPDATE ZERO ROWS


    1. Test Setup


    
    alter session set nls_date_format = 'yyyy-MON-dd hh24:mi:ss';
    
    drop table noise_tab;
    
    create table noise_tab as select level x, rpad('ABC', 100, 'X') y from dual connect by level <= 3; 
    
    drop table rc_tab;
    
    create table rc_tab as select level id, level*10 val from dual connect by level <= 5;
    
    create or replace function get_val (p_id number) return number result_cache as
      l_val     number;
    begin
      select val into l_val from rc_tab where id = p_id;
      return l_val;
    end;
    /
    
    create or replace procedure run_test as
      l_val number;
    begin
      for i in 1 .. 5 loop
        l_val := get_val(i);
      end loop;
    end;
    /
    


    2. Test_1. Pure Test, No Invalid


    
    ----------------- Test 1. Pure Test, No Invalid -----------------
    
    exec dbms_result_cache.flush;
    
    exec run_test;
    
    select creation_timestamp, type, status, name from v$result_cache_objects;
    
    update rc_tab set val = val+1 where 1 = 2;
    
    commit;
    
    select creation_timestamp, type, status, name from v$result_cache_objects;
    


    3. Test_2. Noise Test, Invalid


    
    ----------------- Test 2. Noise Test, Invalid -----------------
    
    exec dbms_result_cache.flush;
    
    exec run_test;
    
    select creation_timestamp, type, status, name from v$result_cache_objects;
    
    update rc_tab set val = val+1 where 1 = 2;
    
    update noise_tab set y = 'xx' where x = 2;
    
    commit;
    
    select creation_timestamp, type, status, name from v$result_cache_objects;
    


    4. Test Output


    
    --============================= Test Output =============================--
    
    SQL > ----------------- Test 1. Pure Test, No Invalid -----------------
    SQL > exec dbms_result_cache.flush;
    
    SQL > exec run_test;
    
    SQL > select creation_timestamp, type, status, name from v$result_cache_objects;
    
    CREATION_TIMESTAMP   TYPE       STATUS    NAME
    -------------------- ---------- --------- -------------------------------------------------------------------
    2017-OCT-04 13:38:34 Dependency Published K.RC_TAB
    2017-OCT-04 13:38:34 Dependency Published K.GET_VAL
    2017-OCT-04 13:38:34 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    
    7 rows selected.
    
    SQL > update rc_tab set val = val+1 where 1 = 2;
    
    0 rows updated.
    
    SQL > commit;
    
    SQL > select creation_timestamp, type, status, name from v$result_cache_objects;
    
    CREATION_TIMESTAMP   TYPE       STATUS    NAME
    -------------------- ---------- --------- -------------------------------------------------------------------
    2017-OCT-04 13:38:34 Dependency Published K.RC_TAB
    2017-OCT-04 13:38:34 Dependency Published K.GET_VAL
    2017-OCT-04 13:38:34 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    
    7 rows selected.
    
    SQL > ----------------- Test 2. Noise Test, Invalid -----------------
    
    SQL > exec dbms_result_cache.flush;
    
    SQL > exec run_test;
    
    SQL > select creation_timestamp, type, status, name from v$result_cache_objects;
    
    CREATION_TIMESTAMP   TYPE       STATUS    NAME
    -------------------- ---------- --------- -------------------------------------------------------------------
    2017-OCT-04 13:38:34 Dependency Published K.RC_TAB
    2017-OCT-04 13:38:34 Dependency Published K.GET_VAL
    2017-OCT-04 13:38:34 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    
    7 rows selected.
    
    SQL > update rc_tab set val = val+1 where 1 = 2;
    
    0 rows updated.
    
    SQL > update noise_tab set y = 'xx' where x = 2;
    
    1 row updated.
    
    SQL > commit;
    
    SQL >
    SQL > select creation_timestamp, type, status, name from v$result_cache_objects;
    
    CREATION_TIMESTAMP   TYPE       STATUS    NAME
    -------------------- ---------- --------- -------------------------------------------------------------------
    2017-OCT-04 13:38:34 Dependency Published K.RC_TAB
    2017-OCT-04 13:38:34 Dependency Published K.GET_VAL
    2017-OCT-04 13:38:34 Result     Invalid   "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Invalid   "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Invalid   "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Invalid   "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    2017-OCT-04 13:38:34 Result     Invalid   "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    
    7 rows selected.
    


    5. Workaround


    
    exec dbms_result_cache.flush;
    
    exec run_test;
    
    select creation_timestamp, type, status, name from v$result_cache_objects;
    
    --lock table rc_tab in ROW EXCLUSIVE mode;
    lock table rc_tab in EXCLUSIVE mode;
    
    begin
      if  1 = 2 then
         update rc_tab set val = val+1;
      end if;
    end;
    /
    
    update noise_tab set y = 'xx' where x = 2;
    
    commit;
    
    select creation_timestamp, type, status, name from v$result_cache_objects;
    
    --============================= Workaround Output =============================--
    
    SQL > exec dbms_result_cache.flush;
    
    SQL > exec run_test;
    
    SQL > select creation_timestamp, type, status, name from v$result_cache_objects;
    
     CREATION_TI TYPE       STATUS    NAME
     ----------- ---------- --------- ---------------------------------------------------------------------------
     09-OCT-2017 Dependency Published K.RC_TAB
     09-OCT-2017 Dependency Published K.GET_VAL
     09-OCT-2017 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
     09-OCT-2017 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
     09-OCT-2017 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
     09-OCT-2017 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
     09-OCT-2017 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
    
     7 rows selected.
    
    SQL > lock table rc_tab in EXCLUSIVE mode;
    
     Table(s) Locked.
    
    SQL > begin
       2    if  1 = 2 then
       3       update rc_tab set val = val+1;
       4    end if;
       5  end;
       6  /
    
    SQL > update noise_tab set y = 'xx' where x = 2;
    
     1 row updated.
    
    SQL > commit;
    
    SQL > select creation_timestamp, type, status, name from v$result_cache_objects;
    
     CREATION_TI TYPE       STATUS    NAME
     ----------- ---------- --------- ---------------------------------------------------------------------------
     09-OCT-2017 Dependency Published K.RC_TAB
     09-OCT-2017 Dependency Published K.GET_VAL
     09-OCT-2017 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
     09-OCT-2017 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
     09-OCT-2017 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
     09-OCT-2017 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
     09-OCT-2017 Result     Published "K"."GET_VAL"::8."GET_VAL"#3048d2af80817a01 #1
     
     7 rows selected.
    
    


    6. v$result_cache_statistics


    V$RESULT_CACHE_STATISTICS contains 3 Rows reporting different kind of Invalidations. To recap all our previous discussion on triggering originals of Result Cache Invalidation, we can try to map those Rows to the originals.

    ID 8. Invalidation Count: DML updates caused, see: PL/SQL Function Result Cache Invalidation (I) and Result Cache:0 rows updated Invalidations (IV)

    ID 9. Delete Count Invalid:  PL/SQL RC Exception Handler related Result Cache, see: RC Latch Contention and PL/SQL Exception Handler (III)

    ID 10. Delete Count Valid: Memory Limit Reached, see: Result Cache: RC Latch Contention and RESULT_CACHE_MAX_SIZE (II)

    ID 11. Hash Chain Length:  length of hash chain. When no invalid, it is 1 or a constant number. When invalidate/create, it is a range.

    If Result Cache experienced a heavy Invalidations, and got recreated, we can see the difference of "ID 5. Create Count Success" before and after recreated, and that of "ID 9. Delete Count Invalid" are quite close. In such case, if we rebuild the Invalidations by a few parallel sessions, latch free of "Result Cache: RC Latch" will appear at the top wait events. It could be fixed by either to flush Result Cache and rebuild from fresh, or use a single session to recreate them.


    Update (18Mar2018)


    In Blog: RESULT_CACHE hint expiration options , dependencies test showed one case of Result Cache Invalidation by "0 rows deleted".
    
    drop table DEMO;
    create table DEMO as select rownum id from xmltable('1 to 100000');
    exec if dbms_result_cache.flush then dbms_output.put_line('Flushed.'); end if;
    
    set autotrace on
    select /*+ result_cache */ count(*) from DEMO;
    
    set autotrace off
    select * from v$result_cache_dependency;
      --
      --  RESULT_ID  DEPEND_ID  OBJECT_NO     CON_ID
      --  --------- ---------- ---------- ----------
      --          1          0    2305802          0
              
    select id,type,status,name,cache_id,invalidations from v$result_cache_objects order by id;
      --  ID TYPE       STATUS    NAME                                          CACHE_ID                   INVALIDATIONS
      --  -- ---------- --------- --------------------------------------------- -------------------------- -------------
      --   0 Dependency Published K.DEMO                                        K.DEMO                                 0
      --   1 Result     Published select /*+ result_cache */ count(*) from DEMO 74s7myvzxca2u8jn4yfkftapdg             0
    
    delete from DEMO where null is not null;
      --  0 rows deleted.
      
    commit;
    
    select * from v$result_cache_dependency;
      --  no rows selected
      
    select id,type,status,name,cache_id,invalidations from v$result_cache_objects order by id;
      -- ID TYPE       STATUS    NAME                                          CACHE_ID                   INVALIDATIONS
      -- -- ---------- --------- --------------------------------------------- -------------------------- -------------
      --  0 Dependency Published K.DEMO                                        K.DEMO                                 1
      --  1 Result     Invalid   select /*+ result_cache */ count(*) from DEMO 3du8t8wtmx913djt9nvwm561sb             0
    
    As we know, in order to record plan_table, "autotrace on" started a new transaction after first select statement with some DML like:
    
      DELETE FROM PLAN_TABLE WHERE STATEMENT_ID=:1
      insert into plan_table (statement_id, timestamp, operation ..
    
    We can verify it by:
    
    set autotrace off
    commit;
    
    select sum(s.sid), count(*)from v$transaction t, v$session s where t.addr=s.taddr and s.sid = sys.dbms_support.mysid;
      --  SUM(S.SID)   COUNT(*)
      --  ---------- ----------
      --                      0
    
    set autotrace on
    
    select dummy from dual;
    
    select sum(s.sid), count(*)from v$transaction t, v$session s where t.addr=s.taddr and s.sid = sys.dbms_support.mysid;
      --  SUM(S.SID)   COUNT(*)
      --  ---------- ----------
      --         543          1
    
    Removing "set autotrace on" and rerun the above test, there is no more such Invalid.

    The original Blog got updated by a new Blog: Result cache invalidation caused by DML locks with deep analysis. Additionally it shows the Invalidation changes triggered by referential integrity in 12c, which needs to be further investigated.