Why this extra physical read while performing index scan?

While preparing the series of posts about Clustering Factor (aka CLUF in 10053 traces) I came across an interesting caveat which I decided to explore.
I will make an attempt to explain its matter, although I encourage you to reach out to the part3 of the mentioned series and navigate to “Quantifying physical reads for low CLUF” and treat it as an intro.

Nonetheless, here are the highlights.
While running my test query retrieving data from a table based on a tiny index range scan, I spotted certain – what I at first thought: anomaly – how Oracle handled this data retrieval.
In fact no anomaly and no mystery – the Oracle crew just proved they’re working with the good feeling of anticipation.
Actually, in the end, I even felt ashamed for not spotting it right away with certainty—”Wait, what are you even surprised about? Of course, they must have done that. Otherwise, you’d be jeopardizing your data quality!’

Such things are typically hidden until you dive deep down and.. to be true – you won’t be able to improve or boost any of your SQL after reading this post.
At least not directly.
It’s merely a tiny puzzle you can’t resist to explore and I won’t be surprised if an obvious fact catches your eye long before I will start drumroll announcing.. that obvious matter.
But let’s start from the beginning.

My test query was to get the data from a table based on accessing just a single index leaf block.
I dumped that leaf block so I knew which entry was the very last one (485) :

row#0[8020] flag: -------, lock: 0, len=12
col 0; len 2; (2):  c1 02              --> SORTED_ID = 1 as decimal
col 1; len 6; (6):  11 71 ad d3 00 00
row#1[8008] flag: -------, lock: 0, len=12
col 0; len 2; (2):  c1 03               --> SORTED_ID = 2 as decimal
col 1; len 6; (6):  11 71 ad d3 00 01
row#2[7996] flag: -------, lock: 0, len=12
col 0; len 2; (2):  c1 04              --> SORTED_ID = 3 as decimal
col 1; len 6; (6):  11 71 ad d3 00 02
row#3[7984] flag: -------, lock: 0, len=12
col 0; len 2; (2):  c1 05
col 1; len 6; (6):  11 71 ad d3 00 03
row#4[7972] flag: -------, lock: 0, len=12
col 0; len 2; (2):  c1 06
col 1; len 6; (6):  11 71 ad d3 00 04
row#5[7960] flag: -------, lock: 0, len=12
col 0; len 2; (2):  c1 07
col 1; len 6; (6):  11 71 ad d3 00 05
row#6[7948] flag: -------, lock: 0, len=12
col 0; len 2; (2):  c1 08
col 1; len 6; (6):  11 71 ad d3 00 06
row#7[7936] flag: -------, lock: 0, len=12
col 0; len 2; (2):  c1 09
col 1; len 6; (6):  11 71 ad d3 00 07
(…)
(…) - skipped ..
(…)
row#484[1830] flag: -------, lock: 0, len=13
col 0; len 3; (3):  c2 05 56   --> Last entry in the leaf of ID_SORTED = 485 as decimal int
col 1; len 6; (6):  11 71 ad de 00 10


I then built the predicate: select * from T where col <= “that last entry”.
So, select * from TSTUS.TSCLUF where id_sorted <= 485 ;

I first flushed the buffer cache to force physical reads and executed my test query treating it with 10046 event accompanied by gathering autotrace stats.
I was obviously expecting certain amount of physical reads performed on behalf of two segments: the index and the table.
As far as the number of physical reads from the table matched my previous calculations,
surprisingly enough, I noticed one extra read performed on behalf of the index.

This is how I put it in that post :

I owe you yet another explanation considering :

“for one index block leaf read Oracle required 19 physical reads from this table” – and that’s something what puzzled me.
Wait – I dumped that very index leaf block and clearly saw it contained all 485 entries (ID_SORTED <= 485), so that 1 index block should be enough to be visited.
So why would I need 2 index leaf blocks?
Scroll up to the 10046 dump and see the 4 physical reads underneath the “Physical reads performed on behalf of index IDX_ID_SORTED (object_id = 181826)“
To be precise – in total: 4, but the first 2 can be accounted for the index root and index branch read.

So why 4 and not 3 ?


Best if I copy-paste the relevant excerpt with the hope it’ll be clear enough what I am trying to explain.
Here it is.

OBJECT                                                       SUBOBJECT_NAME             OBJECT_ID DATA_OBJECT_ID OBJECT_TYPE          
------------------------------------------------------------ ------------------------- ---------- -------------- ---------------- 
TSTUS.TSCLUF                                                                               181825         181825 TABLE                
TSTUS.IDX_ID_SORTED                                                                        181826         181826 INDEX                
SYS@CDBTST1->TPDB1> ALTER SESSION SET TRACEFILE_IDENTIFIER='&trfile_suffix';
Enter value for trfile_suffix: lowcluf
old   1: ALTER SESSION SET TRACEFILE_IDENTIFIER='&trfile_suffix'
new   1: ALTER SESSION SET TRACEFILE_IDENTIFIER='lowcluf'

Session altered.

SYS@CDBTST1->TPDB1>

INST_ID      SID    SERIAL# OSPID                         PID TRACEFILE
------- -------- ---------- ------------------------ -------- ----------------------------------------------------------------------------------------
      1     2907      40588 187041                         96 CDBTST1_ora_187041_lowcluf.trc

SYS@CDBTST1->TPDB1> exec dbms_monitor.session_trace_enable(session_id=>&sid_to_trace,serial_num=>&serial_to_trace,binds=>true,waits=>true);
Enter value for sid_to_trace: 2907
Enter value for serial_to_trace: 40588

SYS@CDBTST1->TPDB1> alter system flush buffer_cache ;

System altered.


SYS@CDBTST1->TPDB1> select /* &comm */ * from TSTUS.TSCLUF where id_sorted <= 485 ;
Enter value for comm: GOOD_CLUF
old   1: select /* &comm */ * from TSTUS.TSCLUF where id_sorted <= 485
new   1: select /* GOOD_CLUF */ * from TSTUS.TSCLUF where id_sorted <= 485

485 rows selected.

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
Plan hash value: 4266397642

---------------------------------------------------------------------------------------------
| Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |               |   485 |   127K|    23   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TSCLUF        |   485 |   127K|    23   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IDX_ID_SORTED |   485 |       |     4   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("ID_SORTED"<=485)


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
         88  consistent gets
         23  physical reads
          0  redo size
     143555  bytes sent via SQL*Net to client
        784  bytes received via SQL*Net from client
         34  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        485  rows processed

The dbms_monitor.session_trace_enable produced, what is widely known, the 10046 trace.
With that trace file at hand let’s get all physical reads:
So simple grepping the CDBTST1_ora_187041_lowcluf.trc reveals the following :

Physical reads performed on behalf of index IDX_ID_SORTED (object_id = 181826) :

[oracle @ CDBTST1 @ testhost01 ]$ # cat CDBTST1_ora_187041_lowcluf.trc | grep WAIT | grep physical  | grep 181826

WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 867 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 680 cellhash#=439186241 diskhash#=3459493587 bytes=8192 obj#=181826 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 415 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 333 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 


Physical reads performed on behalf of table TSCLUF (object_id = 181825) :

[oracle @ CDBTST1 @ testhost01 ]$ # cat CDBTST1_ora_187041_lowcluf.trc | grep WAIT | grep physical  | grep 181825

WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 779 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 316 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 262 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 311 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 233 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 666 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 263 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 296 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 178 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 197 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 196 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 151 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 171 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 365 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 282 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 269 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 311 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 178 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
WAIT #140088361959968: nam='cell single block physical read: flash cache' ela= 200 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 

I could afford to present the full content of the filtering – indeed, just 23 physical reads (see: Statistics), of which 19 were performed on behalf of the table: TSCLUF.
The reads from the table are less interesting though.

What’s interesting are the physical reads performed on behalf of the index :

[oracle @ CDBTST1 @ testhost01 ]$ # cat CDBTST1_ora_187041_lowcluf.trc | grep WAIT | grep physical  | grep 181826 | wc -l
4

Let us reach out to the raw 10046 trace file and dump the corresponding index blocks in order to examine their content. To dump that stuff I need file# and block# – easy to get them from the trace file.
Almost there.. But there is a problem to tackle though.
If I wasn’t running my test on the Exadata machine I would indeed get the file# and the block# directly from that 10046 dump.
The physical reads would be represented by WAIT “db file sequential read”, accompanied by the p1 (file#) and p2 (block#).
The p3 would surely be 1 as this certainly would be a single physical read.

You may btw. want to use that script to find out the meaning of p1,p2 and p3 parameters for a given wait/wait class :

col WAIT_CLASS form a30

select NAME,WAIT_CLASS,PARAMETER1,PARAMETER2,PARAMETER3
from  V$EVENT_NAME
WHERE
upper(WAIT_CLASS) like upper(nvl('%&wait_class%',WAIT_CLASS))
AND
upper(NAME) like upper(nvl('%&event_name%',NAME))
order by WAIT_CLASS, NAME
/
NAME                                               WAIT_CLASS                     PARAMETER1    PARAMETER2   PARAMETER3
-------------------------------------------------- ------------------------------ ------------- ------------ ------------
db file sequential read                            User I/O                       file#         block#       blocks

For that matter, with Exadata’s 10046 traces there is a problem though.
Not only are many wait names renamed (let’s be fair: in many cases to more intuitive replacements), but with “User I/O” class their p1,p2,p3 parameters do not reflect the file and block numbers.
There are cellhash# and diskhash# available instead which obviously won’t help me much – how can I dump a block having cellhash and diskhash values?
Take a look at the four WAITs from the 10046 trace file (I have removed the tim sections) :

1.
WAIT #140657586095912: nam='cell single block physical read: flash cache' ela= 766 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 

2.
WAIT #140657586095912: nam='cell single block physical read: flash cache' ela= 634 cellhash#=439186241 diskhash#=3459493587 bytes=8192 obj#=181826 

3.
WAIT #140657586095912: nam='cell single block physical read: flash cache' ela= 348 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 

4.
WAIT #140657586095912: nam='cell single block physical read: flash cache' ela= 356 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 

With little digging there is a good workaround for that.
I came across the 10200 event (I mention 10200 because it’ll turn out is my hero) in Mauro’s blog.
There are many other sources explaining that problem, credit to Mauro though (actually, also to Stefan Möhler – if you scroll that blog and get to the end) – his article has the title which perfectly matches what I was looking for.

So let’s merge the outcome of 10046 and 10200 into one trace with that script :

cat tr10200.sql

-- 10200, 00000, "consistent read buffer status"

ALTER SESSION SET TRACEFILE_IDENTIFIER = '&trace_id';

ALTER SESSION SET EVENTS '10200 TRACE NAME CONTEXT FOREVER, LEVEL 1';
ALTER SESSION SET EVENTS '10046 TRACE NAME CONTEXT FOREVER, LEVEL 8';

Here’s the entire prep. test :

20:13:19 SYS@CDBTST1->TPDB1> @fb
20:13:27 SYS@CDBTST1->TPDB1> alter system flush buffer_cache ;

System altered.

20:13:28 SYS@CDBTST1->TPDB1> @setts
20:13:34 SYS@CDBTST1->TPDB1> SET AUTOTRACE TRACE EXPLAIN STATISTICS

20:13:34 SYS@CDBTST1->TPDB1> @tr10200.sql
20:13:36 SYS@CDBTST1->TPDB1>
20:13:36 SYS@CDBTST1->TPDB1> -- 10200, 00000, "consistent read buffer status"
20:13:36 SYS@CDBTST1->TPDB1>
20:13:36 SYS@CDBTST1->TPDB1>
20:13:36 SYS@CDBTST1->TPDB1> ALTER SESSION SET TRACEFILE_IDENTIFIER = '&trace_id';
Enter value for trace_id: 10046_10200
old   1: ALTER SESSION SET TRACEFILE_IDENTIFIER = '&trace_id'
new   1: ALTER SESSION SET TRACEFILE_IDENTIFIER = '10046_10200'

Session altered.

Elapsed: 00:00:00.00
20:13:47 SYS@CDBTST1->TPDB1>
20:13:47 SYS@CDBTST1->TPDB1> ALTER SESSION SET EVENTS '10200 TRACE NAME CONTEXT FOREVER, LEVEL 1';

Session altered.

Elapsed: 00:00:00.00
20:13:47 SYS@CDBTST1->TPDB1> ALTER SESSION SET EVENTS '10046 TRACE NAME CONTEXT FOREVER, LEVEL 8';

Session altered.

20:13:50 SYS@CDBTST1->TPDB1> select * from TSTUS.TSCLUF where id_sorted <= 485
20:13:59   2  /

485 rows selected.

Elapsed: 00:00:00.04

Execution Plan
----------------------------------------------------------
Plan hash value: 4196823974

-----------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |               |   485 |   127K|    23   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TSCLUF        |   485 |   127K|    23   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | IDX_ID_SORTED |   485 |       |     4   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("ID_SORTED"<=485)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         88  consistent gets
         23  physical reads
          0  redo size
      14486  bytes sent via SQL*Net to client
        767  bytes received via SQL*Net from client
         34  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        485  rows processed

Having at hand the CDBTST1_ora_238551_10046_10200.trc and grepping after 181826 (index object_id) here is what I came up with (re-edited).
The pairs in brackets are the (file#/block#).

WAIT #139732863711760: nam='cell single block physical read: flash cache' ela= 2612 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 tim=60943189337838
  id1: 0x2e80bb id2: 0x44 pkey: OBJ#3,8,181826 block: (68/3047611)

WAIT #139732863711760: nam='cell single block physical read: flash cache' ela= 708 cellhash#=439186241 diskhash#=3459493587 bytes=8192 obj#=181826 tim=60943189339618
  id1: 0x218836 id2: 0x31 pkey: OBJ#3,8,181826 block: (49/2197558)

WAIT #139732863711760: nam='cell single block physical read: flash cache' ela= 850 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 tim=60943189341362
  id1: 0x2e80bc id2: 0x44 pkey: OBJ#3,8,181826 block: (68/3047612)

WAIT #139732863711760: nam='cell single block physical read: flash cache' ela= 365 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 tim=60943189372959
  id1: 0x2e80bd id2: 0x44 pkey: OBJ#3,8,181826 block: (68/3047613)

Having utilized 10200 traces, many blogs reach out to the c-function : ktrgtc in order to find the file# and block# as shown below.
Oracle C functions annotations (orafun.info) gives the following matching :
(ktrgtc)2 – kernel transaction read consistency get a read consistent block and call back client ??

I was lucky enough to carry out my tests on Oracle 19 (I believe other articles may refer to earlier versions, i.e. Oracle 11g/12c).
Apparently, from Oracle 19c onwards there is no need to apply the following hex-to-integer conversion, whose outcome needs to be then supplied to dbms_utility.data_block_address_file and dbms_utility.data_block_address_block.
I will run that example conversion just in case – that is, if you decided to re-run that test on your own and your RDBMS version is 11g/12c (or perhaps I am simply wrong mentioning ver.19c as an easy solution).
On top, it’d have to be executed on Exadata, so we are talking about nearly impossible circumstances.
Anyway, referring to c-function seems always safe, as it’ll supposed be always present in the trace (it was in mine too).

So, here it is :

corresponding section from the 10046 and 10200 (merge) :

ktrgtc2(): started for block <0x0008 : 0x112e80bb> objd: 0x0002c642
WAIT #140657586095912: nam='cell single block physical read: flash cache' ela= 766 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 tim=49634500072402


14:59:37 SYS@CDBTST1->TPDB1> @hex2int
Enter value for hex_value: 112e80bb
old   2: ( select TO_NUMBER(  replace('&hex_value',chr(32),'') , 'xxxxxxxxxxxxxxxxxx') n from dual )
new   2: ( select TO_NUMBER(  replace('112e80bb',chr(32),'') , 'xxxxxxxxxxxxxxxxxx') n from dual )

                                                 N
--------------------------------------------------
                                         288260283

Elapsed: 00:00:00.00
15:00:10 SYS@CDBTST1->TPDB1>
15:00:11 SYS@CDBTST1->TPDB1> @ls_file_block_dbms_utility.sql
Enter value for dba: 288260283
old   1: SELECT dbms_utility.data_block_address_file(&&dba) file_n,
new   1: SELECT dbms_utility.data_block_address_file(288260283) file_n,
old   2:        dbms_utility.data_block_address_block(&&dba) block_n
new   2:        dbms_utility.data_block_address_block(288260283) block_n

                                            FILE_N                                            BLOCK_N
-------------------------------------------------- --------------------------------------------------
                                                68                                            3047611

Coming back to the main topic now.
I have these four blocks to check :

block: (68/3047611)
block: (49/2197558)
block: (68/3047612)
block: (68/3047613)

So, dumping these four blocks revealed the following.

1. First, I dumped this (file#/block#) : (68/3047611)

WAIT #139732863711760: nam=’cell single block physical read: flash cache’ ela= 2612 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 tim=60943189337838
  id1: 0x2e80bb id2: 0x44 pkey: OBJ#3,8,181826 block: (68/3047611)
cat dbl.sql

alter session set tracefile_identifier = ‘&trace_file_string’ ;
alter system dump datafile &file block &block ;

Then I navigated to the section right below the entry specifying the Itl (interested transaction list) which clearly describes the function of that block.
This is surely the root index block, even though named “branch”.
Fine, expected. It does not help us yet though.

Branch block dump
=================
header address 140167999897676=0x7f7b67d7c04c
kdxcolev 2
KDXCOLEV Flags = - - -
kdxcolok 0
kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y
kdxconco 2
kdxcosdc 0
kdxconro 37
kdxcofbo 102=0x66
kdxcofeo 7652=0x1de4
kdxcoavs 7550
kdxbrlmc 207718454=0xc618836
kdxbrsno 0
kdxbrbksz 8056
kdxbr2urrc 0

2. Then, I dumped this (file#/block#) : (49/2197558)

WAIT #139732863711760: nam=’cell single block physical read: flash cache’ ela= 708 cellhash#=439186241 diskhash#=3459493587 bytes=8192 obj#=181826 tim=60943189339618
  id1: 0x218836 id2: 0x31 pkey: OBJ#3,8,181826 block: (49/2197558)

Again, the (true) branch index block.
Still, it does not explain anything, apart of being the confirmation that the second read refers to the index branch block :

Branch block dump
=================
header address 140018656501836=0x7f58a248604c
kdxcolev 1
KDXCOLEV Flags = - - -
kdxcolok 0
kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y
kdxconco 2
kdxcosdc 0
kdxconro 671
kdxcofbo 1370=0x55a
kdxcofeo 1372=0x55c
kdxcoavs 2
kdxbrlmc 288260284=0x112e80bc
kdxbrsno 0
kdxbrbksz 8056
kdxbr2urrc 0


3. Subsequently, I dumped this (file#/block#) : (68/3047612)

And it’s getting interesting..

WAIT #139732863711760: nam=’cell single block physical read: flash cache’ ela= 850 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 tim=60943189341362
  id1: 0x2e80bc id2: 0x44 pkey: OBJ#3,8,181826 block: (68/3047612)

It’s the leaf block – so containing real indexed keys (values).

Leaf block dump
===============
header address 140691569573988=0x7ff54f070064
kdxcolev 0
KDXCOLEV Flags = - - -
kdxcolok 0
kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y
kdxconco 2
kdxcosdc 0
kdxconro 485
kdxcofbo 1006=0x3ee
kdxcofeo 1830=0x726
kdxcoavs 824
kdxlespl 0
kdxlende 0
kdxlenxt 288260285=0x112e80bd
kdxleprv 0=0x0
kdxledsz 0
kdxlebksz 8032

row#0[8020] flag: -------, lock: 0, len=12
col 0; len 2; (2):  c1 02
col 1; len 6; (6):  11 71 ad d3 00 00
row#1[8008] flag: -------, lock: 0, len=12
col 0; len 2; (2):  c1 03
col 1; len 6; (6):  11 71 ad d3 00 01
row#2[7996] flag: -------, lock: 0, len=12
col 0; len 2; (2):  c1 04
col 1; len 6; (6):  11 71 ad d3 00 02
row#3[7984] flag: -------, lock: 0, len=12
(…) 
(…)
row#484[1830] flag: -------, lock: 0, len=13  -> LAST ENTRY!
col 0; len 3; (3):  c2 05 56
col 1; len 6; (6):  11 71 ad de 00 10

So the last entry represents the indexed value (integer): 485.
Quick check :

SQL> select dump(485,16) from dual ;

DUMP(485,16)
--------------------
Typ=2 Len=3: c2,5,56

If you recall the predicate in the SQL :

select * from TSTUS.TSCLUF where id_sorted <= 485

So, the last entry from the index leaf block covers the upper bound (very last value) of this range scan.
But Oracle doesn’t know whether there is indeed only one such value = 485, so it needs to check the next (logically adjacent) index leaf block.
Therefore we can observe another (possibly redundant) physical read.

Let’s verify that and let’s end up with the range scan at the last before last entry value, that is: 484.

Applying this trick I’ll leave Oracle the chance to jump and read the very last entry, i.e. 485 within the same block.

With that knowledge and also “being aware” that index segment is a sorted structure, the execution does not have to check the next leaf since (I believe my theory sounds about right) it would have established there is no need to reach even further.

Let’s check it.

21:31:17 SYS@CDBTST1->TPDB1> @fb
21:31:19 SYS@CDBTST1->TPDB1> alter system flush buffer_cache ;

System altered.

Elapsed: 00:00:00.19
21:31:19 SYS@CDBTST1->TPDB1> set echo off
21:31:19 SYS@CDBTST1->TPDB1> @mysid

INST_ID         SID SERIAL# OSPID             PID TRACEFILE
------- ----------- ------- ---------- ---------- ------------------------------------
      1         739     487 15040             294 CDBTST1_ora_15040.trc

Elapsed: 00:00:00.03
21:31:52 SYS@CDBTST1->TPDB1> @tr
21:32:26 SYS@CDBTST1->TPDB1> exec dbms_monitor.session_trace_enable(session_id=>&sid_to_trace,serial_num=>&serial_to_trace,binds=>true,waits=>true);
Enter value for sid_to_trace: 739
Enter value for serial_to_trace: 487

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.01
21:32:34 SYS@CDBTST1->TPDB1> set echo off
21:32:34 SYS@CDBTST1->TPDB1> @setts
21:32:45 SYS@CDBTST1->TPDB1> SET AUTOTRACE TRACE EXPLAIN STATISTICS
21:32:46 SYS@CDBTST1->TPDB1> select * from TSTUS.TSCLUF  where id_sorted <= 484 ;

484 rows selected.

Elapsed: 00:00:00.03

Execution Plan
----------------------------------------------------------
Plan hash value: 4196823974

-----------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |               |   484 |   127K|    23   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TSCLUF        |   484 |   127K|    23   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | IDX_ID_SORTED |   484 |       |     4   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("ID_SORTED"<=484)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         87  consistent gets
         22  physical reads
          0  redo size
      14471  bytes sent via SQL*Net to client
        765  bytes received via SQL*Net from client
         34  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        484  rows processed

Ah, look at that – alone the “Statistics” section looks like a good promise: 22 physical reads (not 23 as before).
So should that 1 physical read difference be accounted to the lack of that “checking read” ?
Indeed, having examined the 10046 trace I encountered just 3 physical reads (not 4) on behalf of the index segment :

[oracle @ testhost]$ # cat CDBTST1_ora_15040.trc | grep 181826 | grep physi

1.
WAIT #139857063831128: nam='cell single block physical read: flash cache' ela= 2196 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 tim=60962320205834

2.
WAIT #139857063831128: nam='cell single block physical read: flash cache' ela= 2037 cellhash#=439186241 diskhash#=3459493587 bytes=8192 obj#=181826 tim=60962320208293

3.
WAIT #139857063831128: nam='cell single block physical read: flash cache' ela= 295 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 tim=60962320208822

4. Lastly, I dumped this (file#/block#) : (68/3047613)

block: (68/3047611)
block: (49/2197558)
block: (68/3047612)
–> block: (68/3047613)

You may remember: this read was the last (checking?) physical read.
So, I dumped the last block in the sequence of all physical reads which is associated with this entry in the 10046 trace:

WAIT #139732863711760: nam=’cell single block physical read: flash cache’ ela= 365 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 tim=60943189372959
  id1: 0x2e80bd id2: 0x44 pkey: OBJ#3,8,181826 block: (68/3047613)

It’s again the leaf block :

Leaf block dump
===============
header address 140114805588068=0x7f6f05370064
kdxcolev 0
KDXCOLEV Flags = - - -
kdxcolok 0
kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y
kdxconco 2
kdxcosdc 0
kdxconro 479
kdxcofbo 994=0x3e2
kdxcofeo 1810=0x712
kdxcoavs 816
kdxlespl 0
kdxlende 0
kdxlenxt 288260286=0x112e80be
kdxleprv 288260284=0x112e80bc
kdxledsz 0
kdxlebksz 8032
row#0[8019] flag: -------, lock: 0, len=13
col 0; len 3; (3):  c2 05 57 --> First entry of the subsequent leaf block following the row#484[1830] from the previous block.
col 1; len 6; (6):  11 71 ad de 00 11

This looks to me confirmation enough that this read is the “checking read”.
Oracle has clearly reached out to the block which does not contain any entry fulfilling the predicate. So most certainly it has read the first indexed entry (c2 05 57) and didn’t even traverse the remainder of that leaf block.

Recall: the previous logical entry was located in other physical block :

row#484[1830] flag: -------, lock: 0, len=13  --> LAST ENTRY!
col 0; len 3; (3):  c2 05 56





There are two more “gotchas” on that.

First “gotcha” – the sequence of reads
First, if you look at the order of all involved physical reads you will spot (this was my case) that this “checking read” was performed at the very end of the sequence of all physical reads.

That is, after visiting the first 3 index blocks, then subsequently following retrieving the corresponding blocks from the table, that “checking read” was performed as the very last resort (see the numbering and the whole excerpt limited to physical reads. I have removed the “tim” sections for better readibility) :

[oracle @ testhost ]$ # cat -n  CDBTST1_ora_148205.trc | grep physi | egrep '181826'
    69  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 2305 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 
    73  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 713 cellhash#=439186241 diskhash#=3459493587 bytes=8192 obj#=181826 
    75  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 318 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 
   217  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 393 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826 
[oracle @ testhost ]$ # cat -n CDBTST1_ora_148205.trc | grep physi | egrep '181825|181826'

INDEX: (see: obj#=181826)
69  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 2305 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826
73  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 713 cellhash#=439186241 diskhash#=3459493587 bytes=8192 obj#=181826
75  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 318 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826
TABLE: (see: obj#=181825)
    78  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 683 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
    86  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 321 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
    95  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 390 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
   103  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 276 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
   108  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 217 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
   117  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 1995 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
   125  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 300 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
   133  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 223 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
   138  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 178 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
   146  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 177 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
   154  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 191 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
   161  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 176 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
   167  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 174 cellhash#=3018416126 diskhash#=3925360322 bytes=8192 obj#=181825 
   175  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 416 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
   183  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 369 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
   188  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 304 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
   196  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 340 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
   204  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 198 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
   212  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 223 cellhash#=1290911885 diskhash#=1644083320 bytes=8192 obj#=181825 
INDEX (again) – checking read : (see: obj#=181826 again)
217  WAIT #140145365202312: nam='cell single block physical read: flash cache' ela= 393 cellhash#=3612651036 diskhash#=4260375198 bytes=8192 obj#=181826

Second “gotcha” – unique index

I thought that creating that index as unique (because these indexed values were indeed unique, although CBO stats do not reflect that) and reaching out to the very last entry value within the index leaf block will save that checking read.
No, it didn’t, although now there is enough information not to do it.
Obviously, the gain would be negligible – all in all we’d save just one physical single block read. Apparently, Oracle team would have to change some excerpt of C-code in order to introduce this (tiny) improvement, or perhaps even sacrifice another I/O to establish the facts.

Tiny in-word.
This is not directly related, but being that far I made myself yet additional small effort and traced the CBO decisions as the SQL Plan clearly reports the cost of INDEX RANGE SCAN = 4.

I created the copy of my test table and put the unique index on it.

SQL> create table TSTUS.TSCLUF_COPY as select * from TSTUS.TSCLUF
16:00:19   2  /

Table created.

SQL> create unique index TSTUS.IDX_ID_SORTED_UQ on TSTUS.TSCLUF_COPY ( ID_SORTED ) ;

Index created.

If you dump the CBO decisions to trace (10053) and apply the formula of calculating the cost of index access:
Index_LVL + CEIL( #LB * ix_sel ) 
this is what you’re about to discover.

First, certain prerequisites.
The same way as above, I managed to establish the file# and block# which contains the first index leaf and continued with the test.
The value: 520 is the last indexed entry in the first index leaf block.

Here it is :

SYS@CDBTST1->TPDB1> alter system dump datafile &file block &block ;
Enter value for file: 75
Enter value for block: 311229
old   1: alter system dump datafile &file block &block
new   1: alter system dump datafile 75 block 311229

System altered.

Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000
0x02   0xffff.000.00000000  0x00000000.0000.00  C---    0  scn  0x00000000d8a1f3fd
Leaf block dump
===============
header address 6767681636=0x19362a064
kdxcolev 0
KDXCOLEV Flags = - - -
kdxcolok 0
kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y
kdxconco 1
kdxcosdc 0
kdxconro 520
kdxcofbo 1076=0x434
kdxcofeo 1896=0x768
kdxcoavs 820
kdxlespl 0
kdxlende 0
kdxlenxt 314884030=0x12c4bfbe
kdxleprv 0=0x0
kdxledsz 6
kdxlebksz 8032
row#0[8021] flag: -------, lock: 0, len=11, data:(6):  0a e7 c9 3b 00 00
col 0; len 2; (2):  c1 02
row#1[8010] flag: -------, lock: 0, len=11, data:(6):  0a e7 c9 3b 00 01
col 0; len 2; (2):  c1 03
row#2[7999] flag: -------, lock: 0, len=11, data:(6):  0a e7 c9 3b 00 02
col 0; len 2; (2):  c1 04
row#3[7988] flag: -------, lock: 0, len=11, data:(6):  0a e7 c9 3b 00 03
col 0; len 2; (2):  c1 05
(…)
(…)
row#517[1920] flag: -------, lock: 0, len=12, data:(6):  0a e7 c9 3a 00 17
col 0; len 3; (3):  c2 06 13
row#518[1908] flag: -------, lock: 0, len=12, data:(6):  0a e7 c9 3a 00 18
col 0; len 3; (3):  c2 06 14
row#519[1896] flag: -------, lock: 0, len=12, data:(6):  0a e7 c9 3a 00 19
col 0; len 3; (3):  c2 06 15    --> the last entry in the first leaf block


-- indeed : 

SYS@CDBTST1->TPDB1> select dump(520,16) from dual ;

DUMP(520,16)
--------------------
Typ=2 Len=3: c2,6,15

First, dump of 10053 (CBO dump) :

20:53:43 SYS@CDBTST1->PDBTST> @mysid

INST_ID         SID SERIAL# OSPID             PID TRACEFILE
------- ----------- ------- ---------- ---------- --------------------------------------------------------
      1        2426    8252 188193            272 CDBTST1_ora_188193.trc

20:53:49 SYS@CDBTST1->PDBTST> @setts
20:53:52 SYS@CDBTST1->PDBTST> SET AUTOTRACE TRACE EXPLAIN STATISTICS
20:53:53 SYS@CDBTST1->PDBTST> @tr10053
20:53:57 SYS@CDBTST1->PDBTST> set timing on
20:53:57 SYS@CDBTST1->PDBTST> ALTER SESSION SET TRACEFILE_IDENTIFIER='&file_identifier';
Enter value for file_identifier: chkcost
old   1: ALTER SESSION SET TRACEFILE_IDENTIFIER='&file_identifier'
new   1: ALTER SESSION SET TRACEFILE_IDENTIFIER='chkcost'

Session altered.

20:54:00 SYS@CDBTST1->PDBTST> alter session set events '10053 trace name context forever, level 1';

Session altered.

20:54:02 SYS@ CDBTST1->PDBTST> select /* &comm */ * from TSTUS.TSCLUF_COPY where ID_SORTED <= 520 ;
Enter value for comm: chkcost
old   1: select /* &comm */ * from TSTUS.TSCLUF_COPY where ID_SORTED <= 520
new   1: select /* chkcost */ * from TSTUS.TSCLUF_COPY where ID_SORTED <= 520

520 rows selected.

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------
Plan hash value: 4243688216

--------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                  |   520 |   136K|    25   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TSCLUF_COPY      |   520 |   136K|    25   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | IDX_ID_SORTED_UQ |   520 |       |     4   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("ID_SORTED"<=520)


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
         93  consistent gets
         24  physical reads
          0  redo size
     153808  bytes sent via SQL*Net to client
        808  bytes received via SQL*Net from client
         36  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        520  rows processed

Excerpt of the 10053 dump (relevant entries are marked red) :

Index Stats::
  Index: IDX_ID_SORTED_UQ  Col#: 1
  LVLS: 2  #LB: 20122  #DK: 10000000  LB/K: 1.00  DB/K: 1.00  CLUF: 385177.00  NRW: 10000000.00 SSZ: 0.00 LGR: 0.00 CBK: 0.00 GQL: 0.00 CHR: 0.00 KQDFLG: 8192 BSZ: 1

****** Costing Index IDX_ID_SORTED_UQ
  SPD: Return code in qosdDSDirSetup: NOCTX, estType = INDEX_SCAN
  SPD: Return code in qosdDSDirSetup: NOCTX, estType = INDEX_FILTER
  Estimated selectivity: 5.2000e-05 , col: #1
  Access Path: index (RangeScan)
    Index: IDX_ID_SORTED_UQ
    resc_io: 25.000000  resc_cpu: 381226
    ix_sel: 5.2000e-05  ix_sel_with_filters: 5.2000e-05
    Cost: 25.010166  Resp: 25.010166  Degree: 1
  Best:: AccessPath: IndexRange
  Index: IDX_ID_SORTED_UQ
         Cost: 25.010166  Degree: 1  Resp: 25.010166  Card: 520.000052  Bytes: 0.000000

Reminder:
cost of index access: Index_LVL + CEIL( #LB * ix_sel )
Quick math :

SQL> SELECT TO_CHAR(&scientific_notation_number)  FROM DUAL;
Enter value for scientific_notation_number: 5.2000e-05
old   1: SELECT TO_CHAR(&scientific_notation_number)  FROM DUAL
new   1: SELECT TO_CHAR(5.2000e-05)  FROM DUAL

TO_CHAR
-------
.000052

SQL> !bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.

scale=10
2 + (20122*.000052)
3.046344 -> cost of accessing the index (only index, no table cost involved here)

So 4 is the result of 2 (index level) + “rounding up” the 1.046344 (ceil).

This is btw. what CBO typically seems to be doing, namely, it does not truncate the floating numbers but “bumps them up”, rounding them up to the nearest integer.

Word of caution: the Cost: 25.010166 reported in the 10053 under the ****** Costing Index IDX_ID_SORTED_UQ is the accumulated cost encompassing accessing the index and and the table for the calculated index range scan (details explained here).

One more test.
I will finally merge the 10046 and 10200 into one trace dump for sake of the test with the unique index and “the last entry” test.
With that at hand I will do the same style of grepping in order to ultimately find out that even the UNIQUE index won’t give me this 1 extra read saving.
The sequence of test-steps is identical as above.

cat tr10200.sql

-- 10200, 00000, "consistent read buffer status"

ALTER SESSION SET TRACEFILE_IDENTIFIER = '&trace_id';

ALTER SESSION SET EVENTS '10200 TRACE NAME CONTEXT FOREVER, LEVEL 1';
ALTER SESSION SET EVENTS '10046 TRACE NAME CONTEXT FOREVER, LEVEL 8';
SQL> @tr10200
Enter value for trace_id: unique_index
old   1: ALTER SESSION SET TRACEFILE_IDENTIFIER = '&trace_id'
new   1: ALTER SESSION SET TRACEFILE_IDENTIFIER = 'unique_index'

Session altered.

Session altered.

Session altered.


20:54:02 SYS@ CDBTST1->PDBTST> select /* &comm */ * from TSTUS.TSCLUF_COPY where ID_SORTED <= 520 ;
Enter value for comm: chkcost
old   1: select /* &comm */ * from TSTUS.TSCLUF_COPY where ID_SORTED <= 520
new   1: select /* chkcost */ * from TSTUS.TSCLUF_COPY where ID_SORTED <= 520

520 rows selected.

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------
Plan hash value: 4243688216

--------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                  |   520 |   136K|    25   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TSCLUF_COPY      |   520 |   136K|    25   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | IDX_ID_SORTED_UQ |   520 |       |     4   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("ID_SORTED"<=520)


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
         93  consistent gets
         24  physical reads
          0  redo size
     153808  bytes sent via SQL*Net to client
        808  bytes received via SQL*Net from client
         36  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
        520  rows processed
OBJECT                    OBJECT_ID DATA_OBJECT_ID  OBJECT_TYPE          
------------------------  ---------- -------------- -------------------- 
TSTUS.TSCLUF_COPY         203686         203686     TABLE                
TSTUS.IDX_ID_SORTED_UQ    203687         203687     INDEX       

Clearly, 4 index reads with the last one as the “check read” :

[oracle @ testhost ]$ # cat CDBTST1_ora_282554_IDX_UQ.trc | grep 203687 | grep -A 2 physical

WAIT #140349485564640: nam='cell single block physical read: flash cache' ela= 917 cellhash#=4259318641 diskhash#=4277551782 bytes=8192 obj#=203687 tim=60961571989589
  dbwrid: 4 obj: 203687 objn: 203687 tsn: [3/8] afn: 75 hint: f
  id1: 0x4bfbc id2: 0x4b pkey: OBJ#3,8,203687 block: (75/311228)
--
WAIT #140349485564640: nam='cell single block physical read: flash cache' ela= 725 cellhash#=1290911885 diskhash#=3783142219 bytes=8192 obj#=203687 tim=60961571991610
  dbwrid: 6 obj: 203687 objn: 203687 tsn: [3/8] afn: 44 hint: f
  id1: 0x15d973 id2: 0x2c pkey: OBJ#3,8,203687 block: (44/1431923)
--
WAIT #140349485564640: nam='cell single block physical read: flash cache' ela= 361 cellhash#=4259318641 diskhash#=4277551782 bytes=8192 obj#=203687 tim=60961571992901
  dbwrid: 0 obj: 203687 objn: 203687 tsn: [3/8] afn: 75 hint: f
  id1: 0x4bfbd id2: 0x4b pkey: OBJ#3,8,203687 block: (75/311229)
--
WAIT #140349485564640: nam='cell single block physical read: flash cache' ela= 362 cellhash#=4259318641 diskhash#=4277551782 bytes=8192 obj#=203687 tim=60961572028870
  dbwrid: 2 obj: 203687 objn: 203687 tsn: [3/8] afn: 75 hint: f
  id1: 0x4bfbe id2: 0x4b pkey: OBJ#3,8,203687 block: (75/311230)

Let’s dump the last file#/block# :

SQL> alter session set tracefile_identifier = '&trace_file_string' ;
Enter value for trace_file_string: 4th_idx_read
old   1: alter session set tracefile_identifier = '&trace_file_string'
new   1: alter session set tracefile_identifier = '4th_idx_read'

Session altered.

SQL> alter system dump datafile &file block &block ;
Enter value for file: 75
Enter value for block: 311230
old   1: alter system dump datafile &file block &block
new   1: alter system dump datafile 75 block 311230

System altered.


Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000
0x02   0xffff.000.00000000  0x00000000.0000.00  C---    0  scn  0x00000000d8a1f3fd
Leaf block dump
===============
header address 14683414628=0x36b332064
kdxcolev 0
KDXCOLEV Flags = - - -
kdxcolok 0
kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y
kdxconco 1
kdxcosdc 0
kdxconro 513
kdxcofbo 1062=0x426
kdxcofeo 1881=0x759
kdxcoavs 819
kdxlespl 0
kdxlende 0
kdxlenxt 314884031=0x12c4bfbf
kdxleprv 314884029=0x12c4bfbd
kdxledsz 6
kdxlebksz 8032
row#0[8020] flag: -------, lock: 0, len=12, data:(6):  12 84 e0 56 00 00
col 0; len 3; (3):  c2 06 16  --> 521
row#1[8008] flag: -------, lock: 0, len=12, data:(6):  12 84 e0 56 00 01
col 0; len 3; (3):  c2 06 17
row#2[7996] flag: -------, lock: 0, len=12, data:(6):  12 84 e0 56 00 02
col 0; len 3; (3):  c2 06 18

You may remember that the previous leaf ended up with the entry representing: 520.
So – yes, it clearly reached out to the next logically adjacent block, although this time (due to uniqueness) it didn’t have to.

Recall: the previous entry which was indeed useful was this one :

row#519[1896] flag: -------, lock: 0, len=12, data:(6):  0a e7 c9 3a 00 19
col 0; len 3; (3):  c2 06 15 --> 520

I hope you had some fun with fiddling around the (I still believe: seemingly) futile playing with 10046 & 10200 traces and block dumps.

Leave a Reply

Your email address will not be published. Required fields are marked *