Category Archives: Tuning

PARALLEL_MIN_SERVERS

PARALLEL_MIN_SERVERS specifies the number of slave processes that are started at instance startup. These slave processes are always available and don’t need to be started when a server process requires them. The slave processes exceeding this minimum are dynamically started when required and, once returned to the pool, stay idle for five minutes. If they are not reused in that period, they are shut down. By default, this initialization parameter is set to 0. This means that no slave processes are created at startup. When some SQL statements need slave processes, they wait for the startup of the slave processes. The wait event related to this operation is os thread startup

Let’s demonstrate …

– Check that currently parallel_min_servers is set to its default value of 0

SQL> conn / as sysdba
sho parameter parallel_min_servers

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
parallel_min_servers                 integer     0

– Let’s restart the database

SQL> shu immediate;
startup

– check that no px processes are started automatically    (Servers Started = 0)

SQL> set line 80
set tab off
col statistic for a30

select * from v$px_process_sysstat
where statistic like '%Server%';

STATISTIC                           VALUE
------------------------------ ----------
Servers In Use                          0
Servers Available                       0
Servers Started                         0
Servers Shutdown                        0
Servers Highwater                       0
Servers Cleaned Up                      0
Server Sessions                             0

– Let’s check the maximum no. of servers that can be started –

SQL> sho parameter parallel_max_servers

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
parallel_max_servers                 integer     40

– Let’s try to set parallel_min_servers to a value > parallel_max_servers
— We immediately get an error message

SQL> alter system set parallel_min_servers=41;

alter system set parallel_min_servers=41
*
ERROR at line 1:
ORA-02097: parameter cannot be modified because specified value is invalid
ORA-12811: PARALLEL_MIN_SERVERS must be less than or equal to
PARALLEL_MAX_SERVERS, 40

– Let’s  set parallel_min_servers to a value < parallel_max_servers i.e. 8

SQL> alter system set parallel_min_servers=8;

– Verify that 8 Px servers have been spawned (Servers Started =8)
   and all of them are available (Servers Available = 8)

SQL>  select * from v$px_process_sysstat
where statistic like '%Server%';

STATISTIC                           VALUE
------------------------------ ----------
Servers In Use                          0
Servers Available                       8
Servers Started                         8
Servers Shutdown                        0
Servers Highwater                       0
Servers Cleaned Up                      0
Server Sessions                         0

– Let’s create a table with dictionary DOP = 4 –

SQL> drop table sh.temp_sales purge;
create table sh.temp_sales as select *    from sh.sales;

alter table sh.temp_sales parallel 4;

– Issue a query on temp_sales so that px servers are put to use

SQL> begin
execute immediate 'select * from sh.temp_sales';
end;
/

– Check that the query used 4 Px servers

SQL> select statistic, last_query from v$pq_sesstat
where statistic='Server Threads';

STATISTIC                      LAST_QUERY
------------------------------ ----------
Server Threads                          4

- check that Server highwater has been modified to 4 as maximum of 4 servers have    been allocated by this instance yet

SQL>  select * from v$px_process_sysstat
where statistic like '%Server%';

STATISTIC                           VALUE
------------------------------ ----------
Servers In Use                          0
Servers Available                       8
Servers Started                         8
Servers Shutdown                        0
Servers Highwater                       4
Servers Cleaned Up                      0
Server Sessions                         4

– Let’s modify dictionary DOP of temp_sales to 16 and issue the same query again

SQL>alter table sh.temp_sales parallel 16;

begin
execute immediate 'select * from sh.temp_sales';
end;
/

– check that the query uses 16 px processes

SQL> select statistic, last_query from v$pq_sesstat
where statistic='Server Threads';

STATISTIC                      LAST_QUERY
------------------------------ ----------
Server Threads                         16

– check that 8 new px servers have been assigned (servers started has increasesd    from 8 to 16) and all of them are available (servers available = 16).
   Servers highwater has been updated to 16

SQL>  select * from v$px_process_sysstat
where statistic like '%Server%';

STATISTIC                           VALUE
------------------------------ ----------
Servers In Use                          0
Servers Available                      16
Servers Started                        16
Servers Shutdown                        0
Servers Highwater                      16
Servers Cleaned Up                      0
Server Sessions                        20

– Let’s wait for 5 minutes to check  that px servers idle for 5 minutes are  shut down automatically

SQL> exec dbms_lock.sleep(300);

– check that 8 px servers have been shutdown automatically after 5 minutes
   (Servers Shutdown = 8) as they were idle for that time. Now available
   servers are back to 8)

SQL> select * from v$px_process_sysstat
where statistic like '%Server%';

STATISTIC                           VALUE
------------------------------ ----------
Servers In Use                          0
Servers Available                       8
Servers Started                        16
Servers Shutdown                        8
Servers Highwater                      16
Servers Cleaned Up                      0
Server Sessions                        20

– Let’s change the dictionary DOP of temp_sales back to 4

SQL>alter table sh.temp_sales parallel 4;

– Let’s run the earlier query repeatedly in another session and while the query is
   still running, issue the query in the next step

SQL> begin
for i in 1..1000 loop
execute immediate 'select * from sh.temp_sales';
end loop;
end;
/

– It can be seen that 4 servers are in use in another session (Servers in use=4)
   and only 4 servers are available now

SQL> select * from v$px_process_sysstat
where statistic like '%Server%';

STATISTIC                           VALUE
------------------------------ ----------
Servers In Use                          4
Servers Available                       4
Servers Started                        16
Servers Shutdown                        8
Servers Highwater                      16
Servers Cleaned Up                      0
Server Sessions                      4420

– Let’s check again the status of px servers after the query has completed
   in the other session
– It can be seen that all the 8 servers are available again now

SQL> select * from v$px_process_sysstat
where statistic like '%Server%';

STATISTIC                           VALUE
------------------------------ ----------
Servers In Use                          0
Servers Available                       8
Servers Started                        16
Servers Shutdown                        8
Servers Highwater                      16
Servers Cleaned Up                      0
Server Sessions                      8020

Reference :

Troubleshooting Oracle Performance by Christian Antognini

—————————————————————————————-

Related Links :

Home

————————————————————————————————

MULTIBLOCK READS AND CACHED BLOCKS

 

A full scan operation on a table reads multiple blocks in one I/O as opposed to single block reads which read only one block in one I/O.  The number of blocks read in one multiblock I/O   can range anywhere from one to the number of blocks specified in the db_file_multiblock_read_count parameter. For example, if the parameter is set to 64 and there are 640 blocks in the table,  the least no. of I/O’s required  to get all the blocks = 640/64 = 10. The no. of I/O’s could be more than one due  to the following limitations on multiblock read calls:

– Multiblock reads cannot span extent boundaries i.e.
if db_file_multiblock_read_count = 64 and extent size = 8 blocks, a multiblock read can read a maximum of 8 blocks only in one I/O.

– If a requested block is already in the buffer cache, it  will not be read again as part of the multiblock read.  Oracle will simply read the blocks up to those not already in memory, then issue another read call that skips those blocks to read the rest.  For example,
let’s say db_file_multiblock_read_count = 64 and
the range of blocks to be read is between block number 1 and 64. If block no. 32 is already available in the buffer cache, first multiblock read  reads would be done for blocks 1 to 31 and subsequent read will read blocks from block 33 to 64.

• Multiblock reads cannot  exceed the operating system limit for multiblock read size

In an earlier post, I had  demonstrated that multiblock reads cannot span extent boundaries.
In this post, I will demonstrate that multiblock reads skip blocks which are already cached.

Overview:

– Db_block_size = 8k
– create table MBRC_CACHE with uniform extent size = 8
– Populate table with records for id = 1 to 60 such that each block contains records for one id only
– Create index on the table on id column
– Trace the following
. Read records for id = 2
. Perform full scan on the table

– Trace file shows that
to read records for id = 2
. Index segment header block is read
. Index leaf blocks are read
. Table block containing records for id = 2 is read

to perform FTS on the table
. Table segment header block is read
. Table block containing records for id =1 is read
. Table block containing records for id = 2 is skipped
. Remaining Table blocks containing records for id = 3 to 60 are read

Implementation:

>SQL> sho parameter db_block_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
db_block_size integer 8192

SQL> sho parameter db_file_multi

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
db_file_multiblock_read_count integer 8

SQL> sho parameter optimizer_mode

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
optimizer_mode string ALL_ROWS<

- create table MBRC_CACHE with uniform extent size = 8

SQL> drop table mbrc_cache purge;
        create table mbrc_cache (id number , text char(700));

- Populate table with records for id = 1 to 60 such that each block contains records for one id only

begin
for i in 1..60 loop
for j in 1..10 loop
insert into mbrc_cache values (i, 'txt'||j);
commit;
end loop;
commit;
end loop;
end;
/

– check that records for each id are placed in one block each

SQL>select id, dbms_rowid.rowid_block_number(rowid),count(*)
from mbrc_cache
group by id, dbms_rowid.rowid_block_number(rowid)
order by id, dbms_rowid.rowid_block_number(rowid);

ID DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)   COUNT(*)
---------- ------------------------------------ ----------
1                                88809         10
2                                88810         10
3                                88811         10
4                                88812         10
5                                88813         10
6                                88814         10
7                                88815         10
8                                88816         10
9                                88817         10
10                                88818         10
11                                88819         10

...
56                                88872         10
57                                88873         10
58                                88874         10
59                                88875         10
60                                88876         10

- Create index on the table on id column and gather statistics

SQL>create index mbrc_cache_idx on mbrc_cache(id);

exec dbms_stats.gather_table_stats(USER, 'MBRC_CACHE', cascade=> true);

select table_name, num_rows, blocks from user_tables where table_name= 'MBRC_CACHE';

TABLE_NAME                       NUM_ROWS     BLOCKS
------------------------------ ---------- ----------
MBRC_CACHE                            600         60

– Find out object_id for mbrc_cache table and mbrc_)cache_idx index

SQL>col object_name for a30
select object_name, object_id from dba_objects where object_name like 'MBRC_CACHE%';

OBJECT_NAME                     OBJECT_ID
------------------------------ ----------
MBRC_CACHE                          75140
MBRC_CACHE_IDX                      75225

– Note that for table MBRC_CACHE, segment header is in block# 88808
— Note that for index MBRC_CACHE_IDX, segment header is in block# 88880

SQL> select segment_name, header_block from dba_segments where segment_name LIKE 'MBRC_CACHE%';

SEGMENT_NAME         HEADER_BLOCK
-------------------- ------------
MBRC_CACHE                  88808
MBRC_CACHE_IDX              88880

– Note that 8 blocks (88880 – 88888) are assigned to index mbrc_cache
— Block 88880 contains segment header
— Two index leaf blocks are located in block# 88881 and 88882

SQL> select SEGMENT_NAME, BLOCK_ID, BLOCKS from dba_extents where segment_name='MBRC_CACHE_IDX';

SEGMENT_NAME           BLOCK_ID     BLOCKS
——————– ———- ———-
MBRC_CACHE_IDX            88880          8

SQL> select index_name, leaf_blocks from user_indexes where index_name=’MBRC_CACHE_IDX';

INDEX_NAME                     LEAF_BLOCKS
—————————— ———–
MBRC_CACHE_IDX                           2

- Trace the following
. Read records for id = 2
. Perform full scan on the table

conn / as sysdba

alter system flush buffer_cache;
alter session set tracefile_identifier = 'mbrc';

set serveroutput off
exec dbms_monitor.session_trace_enable(waits=> true);
select * from mbrc_cache where id=2;
select * from mbrc_cache ;

declare
cursor c1_cur  is select * from mbrc_cache where id=2;
v1_cur mbrc_cache%rowtype;
cursor c2_cur  is select * from mbrc_cache;
v2_cur mbrc_cache%rowtype;
exec dbms_monitor.session_trace_disable();

begin
open c1_cur;
fetch c1_cur into v1_cur;

loop
fetch c1_cur into v1_cur;
exit when c1_cur%NOTFOUND;
end loop ;
close c1_cur;
end;
/
exec dbms_monitor.session_trace_disable();

SQL> col name for a15
col value for a60
select name, value from v$diag_info where upper(name) like '%TRACE F%';

NAME            VALUE
--------------- ------------------------------------------------------------
Default Trace F c:\app\administrator\diag\rdbms\orcl1\orcl1\trace\orcl1_ora_
ile             3828_mbrc.trc

– Read trace file

SQL> ho notepad c:\app\administrator\diag\rdbms\orcl1\orcl1\trace\orcl1_ora_3828_mbrc.trc

- Trace file (trimmed to show only read waits) shows that
to read records for id = 2
. Index segment header block is read
. Index leaf blocks are read
. Table block containing records for id = 2 is read

to perform FTS on the table
. Table segment header block is read
. Table block containing records for id =1 is read
. Table block containing records for id = 2 is skipped
. Remaining Table blocks containing records for id = 3 to 60 are read

=====================
PARSING IN CURSOR #1 len=39 dep=0 uid=0 oct=3 lid=0 tim=283016701180 hv=2279949904 ad=’7ff0da36078′ sqlid=’42bhbr63yajkh’
select * from mbrc_cache where id=2
END OF STMT
PARSE #1:c=0,e=1668,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,plh=3589681151,tim=283016701177
EXEC #1:c=0,e=55,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=3589681151,tim=283016701337

– Read index mbrc_cache_idx (object_id  =75225 )
— Read two index leaf blocks  88881 and 88882 (as found earlier)

WAIT #1: nam=’db file sequential read’ ela= 1045 file#=1 block#=88881 blocks=1 obj#=75225 tim=283016702567
WAIT #1: nam=’db file sequential read’ ela= 20967 file#=1 block#=88882 blocks=1 obj#=75225 tim=283016723719

– Read table mbrc_cache (object_id = 75140)
— Read block 88810 contaning records for id = 2 (as found earlier)

WAIT #1: nam=’db file sequential read’ ela= 14929 file#=1 block#=88810 blocks=1 obj#=75140 tim=283016738901

WAIT #1: nam=’SQL*Net message from client’ ela= 72910 driver id=1111838976 #bytes=1 p3=0 obj#=75140 tim=283016812709
CLOSE #1:c=0,e=32,dep=0,type=0,tim=283016812906
=====================

PARSING IN CURSOR #2 len=29 dep=0 uid=0 oct=3 lid=0 tim=283016814397 hv=4285579899 ad=’7ff097aab18′ sqlid=’1qvcxnbzr1hmv’
select * from mbrc_cache
END OF STMT
PARSE #2:c=0,e=1425,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,plh=2256522414,tim=283016814394
EXEC #2:c=0,e=46,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=2256522414,tim=283016814549

– During FTS, read segment header for table mbrc_cache (header block# = 88808 as found earlier)

WAIT #2: nam=’db file sequential read’ ela= 1843 file#=1 block#=88808 blocks=1 obj#=75140 tim=283016816604

– Read block 88809 containing rows for id = 1 ( as found earlier)

WAIT #2: nam=’db file sequential read’ ela= 384 file#=1 block#=88809 blocks=1 obj#=75140 tim=283016817170

– Skip block 88810 containing records for id=2 as that block has already been read into buffer
— Read 5 remaining blocks in the extent starting from block 88811

WAIT #2: nam=’db file scattered read’ ela= 22144 file#=1 block#=88811 blocks=5 obj#=75140 tim=283016947620

– Read subdsequent extents in units of 8 blocks

WAIT #2: nam=’db file scattered read’ ela= 302 file#=1 block#=88816 blocks=8 obj#=75140 tim=283017221664

WAIT #2: nam=’db file scattered read’ ela= 33 file#=1 block#=88824 blocks=8 obj#=75140 tim=283017573336

WAIT #2: nam=’db file scattered read’ ela= 17389 file#=1 block#=88832 blocks=8 obj#=75140 tim=283017918715

WAIT #2: nam=’db file scattered read’ ela= 496 file#=1 block#=88840 blocks=8 obj#=75140 tim=283018212031

WAIT #2: nam=’db file scattered read’ ela= 28 file#=1 block#=88848 blocks=8 obj#=75140 tim=283018477971

WAIT #2: nam=’db file scattered read’ ela= 31 file#=1 block#=88856 blocks=8 obj#=75140 tim=283018786018

– Read 5 blocks  from last extent
— Last block 88876 contains records for id = 60 (as found earlier)
— Read 4 blocks ( 88872 – 88876) containing data
— Read one more block to ensure that there are no more blocks containing data

WAIT #2: nam=’db file scattered read’ ela= 28 file#=1 block#=88872 blocks=5 obj#=75140 tim=283019045215

WAIT #2: nam=’SQL*Net message from client’ ela= 54378 driver id=1111838976 #bytes=1 p3=0 obj#=75140 tim=283019256210
CLOSE #2:c=0,e=25,dep=0,type=0,tim=283019260582

=====================

Conclusion:

– If a requested block is already in the buffer cache, it  will not be read again as part of the multiblock read.  Oracle will simply read the blocks up to those not already in memory, then issue another read call that skips those blocks to read the rest.

References:

Pro Oracle SQL by Karen Morton, Kerry Osborne,  Robyn Sands, Riyaz and Jared Still

Oracle Documentation

————————————————————————————————-

Related links:

Home

Tuning Index

Oracle always full scans tables smaller than DBFMRC:  A myth

DB_FILE_MULTIBLOCK_READ_COUNT And Extent Size

 

——————–

 

DB_FILE_MULTIBLOCK_READ_COUNT AND EXTENT SIZE

A full scan operation on a table reads multiple blocks in one I/O as opposed to single block reads which read only one block in one I/O. The number of blocks read in one multiblock I/O can range anywhere from one to the number of blocks specified in the db_file_multiblock_read_count parameter. For example, if the parameter is set to 64 and there are 640 blocks in the table, the least no. of I/O’s required to get all the blocks = 640/64 = 10.

The no. of I/O’s could be more than one due to the following limitations on multiblock read calls:

– Multiblock reads cannot span extent boundaries i.e.
if db_file_multiblock_read_count = 64 and extent size = 8 blocks, a multiblock read can read a maximum of 8 blocks only in one I/O.

– If a requested block is already in the buffer cache, it will not be read again as part of the multiblock read. Oracle will simply read the blocks up to those not already in memory, then issue another read call that skips those blocks to read the rest. For example,
let’s say db_file_multiblock_read_count = 64 and
the range of blocks to be read is between block number 1 and 64. If block no. 32 is already available in the buffer cache, first multiblock read reads would be done for blocks 1 to 31 and subsequent read will read blocks from block 33 to 64.

• Multiblock reads cannot exceed the operating system limit for multiblock read size

In this post, I will demonstrate that multiblock reads cannot span extent boundaries.

In my next post, I will demonstrate that multiblock reads skip the bocks which already cached.

 

Overview:

– Db_block_size = 8k
– Set db_file_multiblock_read_count = 64
– create two tables with identical data :
. table mbrc_ext_64k with uniform extent size = 8
. table mbrc_ext_512k with uniform extent size = 64
. table mbrc_ext_1024k with uniform extent size = 128

– Trace FTS on both the tables and check the trace file
– Trace file shows that
. I/O’s made to mbrc_ext_64k are of size 8 blocks (=extent size)
. I/O’s made to mbrc_ext_512k are of size 64 blocks(= db_file_multiblock_read_count )
. I/O’s made to mbrc_ext_1024k are of size 64 blocks(= db_file_multiblock_read_count )

Implementation:

– check that db_block_size = 8k

SQL> sho parameter db_block_size

NAME TYPE VALUE
------------------------------------ ----------- -----------------
db_block_size integer 8192

— Set db_file_multiblock_read_count = 64

SQL> alter system set db_file_multiblock_read_count =64
sho parameter db_file_multi

NAME TYPE VALUE
------------------------------------ ----------- -----------------
db_file_multiblock_read_count integer 64

— check that optimizer_mode = all_rows

SQL> sho parameter optimizer_mode

NAME TYPE VALUE
------------------------------------ ----------- -----------------
optimizer_mode string ALL_ROWS

— Create tablespaces
. tbs_64k – Uniform Extent size = 64K = 8 blocks
. tbs_512k – Uniform Extent size = 512K = 64 blocks
. tbs_1024k- Uniform Extent size = 512K = 128 blocks

SQL>drop tablespace tbs_64k including contents and datafiles;
create tablespace tbs_64k datafile 'C:\APP\mbrc8k.dbf' size 100m reuse autoextend on
extent management local uniform size 64k segment space management manual;

drop tablespace tbs_512k including contents and datafiles;
create tablespace tbs_512k datafile 'C:\APP\mbrc512k.dbf' size 100m reuse autoextend on
extent management local uniform size 512k segment space management manual;

drop tablespace tbs_1024k including contents and datafiles;
create tablespace tbs_1024k datafile 'C:\APP\mbrc1024k.dbf' size 100m reuse autoextend on
extent management local uniform size 1024k segment space management manual;

– create three tables with identical data :
. table mbrc_ext_64k in tbs_64k with uniform extent size = 8 blocks
. table mbrc_ext_512k in tbs_512k with uniform extent size = 64 blocks
. table mbrc_ext_1024k in tbs_1024k with uniform extent size = 128 blocks

drop table mbrc_ext_64k purge;
create table mbrc_ext_64k tablespace tbs_64k as select * from sh.sales where rownum<=50000;

drop table mbrc_ext_512k purge;
create table mbrc_ext_512k tablespace tbs_512k as select * from sh.sales where rownum<=50000;

drop table mbrc_ext_1024k purge;
create table mbrc_ext_1024k tablespace tbs_1024k as select * from sh.sales where rownum<=50000;

— Gather statistics

exec dbms_stats.gather_table_stats(USER, 'MBRC_EXT_64K', cascade=> true);
exec dbms_stats.gather_table_stats(USER, 'MBRC_EXT_512K', cascade=> true);
exec dbms_stats.gather_table_stats(USER, 'MBRC_EXT_1024K', cascade=> true);

— check that extent size for
. MBRC_EXT_64K = 8
. MBRC_EXT_512K = 64
. MBRC_EXT_512K = 128

SQL>col segment_name for a20
select segment_name, blocks, count(*)
from dba_extents
where segment_name like 'MBRC_EXT_%K'
group by segment_name, blocks;

SEGMENT_NAME BLOCKS COUNT(*)
-------------------- ---------- ----------
MBRC_EXT_1024K 128 2
MBRC_EXT_64K 8 31
MBRC_EXT_512K 64 4

— Find out object_id’s of the tables

SQL>col object_name for a30
select object_name, object_id from dba_objects where object_name like 'MBRC_EXT_%K%';

OBJECT_NAME OBJECT_ID
------------------------------ ----------
MBRC_EXT_1024K 75139
MBRC_EXT_512K 75131
MBRC_EXT_64K 75130

— Enable tracing and per Full table scan of the tables

conn / as sysdba

alter system flush buffer_cache;
alter session set tracefile_identifier = 'mbrc';

set serveroutput off
exec dbms_monitor.session_trace_enable(waits=> true);

declare
cursor c1_cur is select * from mbrc_ext_64k;
cursor c2_cur is select * from mbrc_ext_512k;
cursor c3_cur is select * from mbrc_ext_1024k;
v1_cur mbrc_ext_64k%rowtype;
v2_cur mbrc_ext_512k%rowtype;
v3_cur mbrc_ext_1024k%rowtype;

begin
open c1_cur;
loop
fetch c1_cur into v1_cur;
exit when c1_cur%NOTFOUND;
end loop ;
close c1_cur;

open c2_cur;
loop
fetch c2_cur into v2_cur;
exit when c2_cur%NOTFOUND;
end loop ;
close c2_cur;

open c3_cur;
loop
fetch c3_cur into v3_cur;
exit when c3_cur%NOTFOUND;
end loop ;
close c3_cur;

end;
/
exec dbms_monitor.session_trace_disable();

— Find out name of trace file generated

SQL>col value for a80
select name, value from v$diag_info where upper(name) like '%TRACE F%';

NAME VALUE
-------------------- -------------------------------------------------------------------------
Default Trace File c:\app\administrator\diag\rdbms\orcl1\orcl1\trace\orcl1_ora_5476_mbrc.trc

— Read the trace file

SQL>ho notepad c:\app\administrator\diag\rdbms\orcl1\orcl1\trace\orcl1_ora_5476_mbrc.trc

=====================

In MBRC_EXT_64K (object_id = 75130)

Single block read identified by wait event ‘db file sequential read’
. Reads block# 128

— Let’s verify that block# 128 contains segment header

SQL> select segment_name, header_block from dba_segments where segment_name = 'MBRC_EXT_64k';

SEGMENT_NAME HEADER_BLOCK
-------------------- ------------
MBRC_EXT_64K 128

First multiblock read (identified by wait event ‘db file scattered read’)
.starting at block# 129 (Block next to segment header)
.No. of blocks read = 7 (blocks=7= remainig blocks in the extent)

Subsequent multiblock reads (identified by wait event ‘db file scattered read’)
. from block# 136 (129+7) onwards
. No. of blocks read = 8 (blocks = 8)

Last multiblock read (identified by wait event ‘db file scattered read’)
. starting at block# 368
. No. of blocks read = 2 (blocks = 2 = remaining blocks containing data)

Last block# read = 368 + 2 (in last wait, starting block = 368 , blocks read = 2)
= 370
First block# read (after segment header) = 129

Total no. of blocks scanned = 370 – 129
= 241

— Let’s verify that this no. matches the No. of blocks containing data as stored in data dictionary

SQL>select table_name, blocks from user_tables where table_name = 'MBRC_EXT_64K';

TABLE_NAME BLOCKS
------------------------------ ----------
MBRC_EXT_64K 241

=====================

PARSING IN CURSOR #2 len=26 dep=1 uid=0 oct=3 lid=0 tim=249360274955 hv=1652364071 ad=’7ff09780df8′ sqlid=’d3ubygdj7u4t7′
SELECT * FROM MBRC_EXT_64K
END OF STMT

PARSE #2:c=0,e=141,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=1,plh=3512697650,tim=249360274953
EXEC #2:c=0,e=52,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=1,plh=3512697650,tim=249360275088

WAIT #2: nam=’Disk file operations I/O’ ela= 211 FileOperation=2 fileno=7 filetype=2 obj#=75130 tim=249360275427

WAIT #2: nam=‘db file sequential read’ ela= 22023 file#=7 block#=128 blocks=1 obj#=75130 tim=249360297493

WAIT #2: nam=‘db file scattered read’ ela= 1305 file#=7 block#=129 blocks=7 obj#=75130 tim=249360299150

WAIT #2: nam=‘db file scattered read‘ ela= 951 file#=7 block#=136 blocks=8 obj#=75130 tim=249360386033

WAIT #2: nam=‘db file scattered read’ ela= 478 file#=7 block#=144 blocks=8 obj#=75130 tim=249360487978

WAIT #2: nam=‘db file scattered read‘ ela= 459 file#=7 block#=152 blocks=8 obj#=75130 tim=249360584467
….


WAIT #2: nam=’db file scattered read‘ ela= 213 file#=7 block#=368 blocks=2 obj#=75130 tim=249362258692

STAT #2 id=1 cnt=50000 pid=0 pos=1 obj=75130 op=’TABLE ACCESS FULL MBRC_EXT_64K (cr=50006 pr=242 pw=0 time=251918 us cost=64 size=1450000 card=50000)’

CLOSE #2:c=0,e=4,dep=1,type=3,tim=249362277148
=====================

=====================

In MBRC_EXT_512K (object_id = 75131)

Single block read identified by wait event ‘db file sequential read’
. Reads block# 128

— Let’s verify that block# 128 contains segment header

SQL> select segment_name, header_block from dba_segments where segment_name = 'MBRC_EXT_512k';

SEGMENT_NAME HEADER_BLOCK
-------------------- ------------
MBRC_EXT_512K 128

First multiblock read (identified by wait event ‘db file scattered read’)
.starting at block# 129 (Block next to segment header)
.No. of blocks read = 63 (blocks=63 = remainig blocks in the extent)

Subsequent multiblock reads (identified by wait event ‘db file scattered read’)
. from block# 192 (129+63) onwards
. No. of blocks read = 64 (blocks = 64)

Last multiblock read (identified by wait event ‘db file scattered read’)
. starting at block# 320
. No. of blocks read = 50 (blocks = 50 = remaining blocks containing data)

Last block# read = 320 + 50 (in last wait, starting block = 320 , blocks read = 50)
= 370
First block# read (after segment header) = 129

Total no. of blocks scanned = 370 – 129
= 241

— Let’s verify that this no. matches the No. of blocks containing data as stored in data dictionary

SQL>select table_name, blocks from user_tables where table_name = 'MBRC_EXT_512K';

TABLE_NAME BLOCKS
------------------------------ ----------
MBRC_EXT_512K 241

=====================
PARSING IN CURSOR #2 len=27 dep=1 uid=0 oct=3 lid=0 tim=249362277315 hv=3723803804 ad=’7ff097d1ec8′ sqlid=’grmbfwvfz9g4w’
SELECT * FROM MBRC_EXT_512K
END OF STMT
PARSE #2:c=0,e=96,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=1,plh=748809365,tim=249362277313
EXEC #2:c=0,e=48,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=1,plh=748809365,tim=249362277446

WAIT #2: nam=’Disk file operations I/O’ ela= 191 FileOperation=2 fileno=8 filetype=2 obj#=75131 tim=249362277741

WAIT #2: nam=’db file sequential read‘ ela= 18302 file#=8 block#=128 blocks=1 obj#=75131 tim=249362296085

WAIT #2: nam=‘db file scattered read’ ela= 4297 file#=8 block#=129 blocks=63 obj#=75131 tim=249362300903

WAIT #2: nam=‘db file scattered read‘ ela= 2675 file#=8 block#=192 blocks=64 obj#=75131 tim=249362851282

WAIT #2: nam=‘db file scattered read‘ ela= 3372 file#=8 block#=256 blocks=64 obj#=75131 tim=249363235459

WAIT #2: nam=‘db file scattered read‘ ela= 2651 file#=8 block#=320 blocks=50 obj#=75131 tim=249363612810

STAT #2 id=1 cnt=50000 pid=0 pos=1 obj=75131 op=’TABLE ACCESS FULL MBRC_EXT_512K (cr=50003 pr=242 pw=0 time=192560 us cost=64 size=1450000 card=50000)’

CLOSE #2:c=0,e=2,dep=1,type=3,tim=249363956416
EXEC #1:c=3634823,e=3681748,p=484,cr=100009,cu=0,mis=0,r=1,dep=0,og=1,plh=0,tim=249363956444
WAIT #1: nam=’SQL*Net message to client’ ela= 3 driver id=1111838976 #bytes=1 p3=0 obj#=75131 tim=249363956494
WAIT #1: nam=’SQL*Net message from client’ ela= 2159 driver id=1111838976 #bytes=1 p3=0 obj#=75131 tim=249363958679
CLOSE #1:c=0,e=38,dep=0,type=0,tim=249363958919
=====================
=====================
=====================

In MBRC_EXT_1024K (object_id = 75139)

Single block read identified by wait event ‘db file sequential read’
. Reads block# 128

— Let’s verify that block# 128 contains segment header

SQL> select segment_name, header_block from dba_segments where segment_name = 'MBRC_EXT_1024k';

SEGMENT_NAME HEADER_BLOCK
-------------------- ------------
MBRC_EXT_1024K 128

First multiblock read (identified by wait event ‘db file scattered read’)
.starting at block# 129 (Block next to segment header)
.No. of blocks read = 64 (blocks=64 = db_file_multiblock_count since extent contains 128 blocks and there are still 127 blocks left in the extent)

Second multiblock read (identified by wait event ‘db file scattered read’)
.starting at block# 163 (129 + 64)
.No. of blocks read = 63 (blocks=63 = remaining blocks in the extent)

Subsequent multiblock reads (identified by wait event ‘db file scattered read’)
. from block# 256 (193 +63) onwards
. No. of blocks read = 64 (blocks = 64)

Last multiblock read (identified by wait event ‘db file scattered read’)
. starting at block# 320
. No. of blocks read = 50 (blocks = 50 = remaining blocks containing data)

Last block# read = 320 + 50 (in last wait, starting block = 320 , blocks read = 50)
= 370
First block# read (after segment header) = 129

Total no. of blocks scanned = 370 – 129
= 241

— Let’s verify that this no. matches the No. of blocks containing data as stored in data dictionary

SQL>select table_name, blocks from user_tables where table_name = 'MBRC_EXT_1024K';

TABLE_NAME BLOCKS
------------------------------ ----------
MBRC_EXT_1024K 241

==================================

PARSING IN CURSOR #2 len=28 dep=1 uid=0 oct=3 lid=0 tim=262780042519 hv=4010235831 ad=’7ff0d84e9e8′ sqlid=’7rs934mrhfpxr’
SELECT * FROM MBRC_EXT_1024K
END OF STMT

PARSE #2:c=15601,e=4201,p=0,cr=21,cu=0,mis=1,r=0,dep=1,og=1,plh=3425801585,tim=262780042518
EXEC #2:c=0,e=23,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=1,plh=3425801585,tim=262780042611
WAIT #2: nam=’Disk file operations I/O’ ela= 169 FileOperation=2 fileno=9 filetype=2 obj#=75139 tim=262780042872

WAIT #2: nam=‘db file sequential read‘ ela= 6916 file#=9 block#=128 blocks=1 obj#=75139 tim=262780049822

WAIT #2: nam=’db file scattered read‘ ela= 3837 file#=9 block#=129 blocks=64 obj#=75139 tim=262780053909

WAIT #2: nam=‘db file scattered read‘ ela= 2422 file#=9 block#=193 blocks=63 obj#=75139 tim=262780770697

WAIT #2: nam=’db file scattered read‘ ela= 2005 file#=9 block#=256 blocks=64 obj#=75139 tim=262781424869

WAIT #2: nam=db file scattered read‘ ela= 40794 file#=9 block#=320 blocks=50 obj#=75139 tim=262782155450

STAT #2 id=1 cnt=50000 pid=0 pos=1 obj=75139 op=’TABLE ACCESS FULL MBRC_EXT_1024K (cr=50003 pr=242 pw=0 time=274738 us cost=64 size=1450000 card=50000)’
CLOSE #2:c=0,e=3,dep=1,type=3,tim=262782568794
EXEC #1:c=7020045,e=7580902,p=732,cr=150081,cu=0,mis=0,r=1,dep=0,og=1,plh=0,tim=262782568832
WAIT #1: nam=’SQL*Net message to client’ ela= 5 driver id=1111838976 #bytes=1 p3=0 obj#=75139 tim=262782568901
WAIT #1: nam=’SQL*Net message from client’ ela= 2776 driver id=1111838976 #bytes=1 p3=0 obj#=75139 tim=262782571712
CLOSE #1:c=0,e=44,dep=0,type=0,tim=262782571842

=========================
Conclusion:

– During FTS, segment header is accessed using single block read first to get the extent map.
– During first multiblock read,
if remaining blocks in the extent < db_file_multiblock_read_count
remaining blocks in the first extent (excluding segmnet header block) are read.
else (remaining blocks in the extent >= db_file_multiblock_read_count
blocks = db_file_multiblock_read_count are read

– During subsequent multiblock reads,
if remaining blocks in the extent < db_file_multiblock_read_count
remaining blocks in the extent are read.
else (remaining blocks in the extent >= db_file_multiblock_read_count
blocks = db_file_multiblock_read_count are read

– During last multiblock read, remaining blocks containing data are read.

– If extent size <= db_file_multiblock_read_count
No. of blocks read in one I/O = No. of blocks in one extent

– If extent size > db_file_multiblock_read_count
No. of blocks read in one I/O = least (db_file_multiblock_read_count, no. of blocks containing data)

Hence, we have been able to verify that multiblock reads cannot span extents and maximum no. of blocks that can be read in one I/O = db_file_multiblock_read_count

References:

https://forums.oracle.com/forums/thread.jspa?messageID=11051591#11051591

———————————————————————————

Related links:

Home

Tuning Index
Multiblock Reads And Cached Blocks
Oracle Always Full Scans Tables Smaller Than DFMBRC: A Myth

 

ORACLE ALWAYS FULL SCANS A TABLE SMALLER THAN DB_FILE_MULIBLOCK_READ_COUNT : A MYTH

Many of us believe that optimizer will prefer FTS to index access on a table whose size is smaller than DB_FILE_MULIBLOCK_READ_COUNT as whole table can be accessed with just a single I/O as compared to at least two  I/O’s ( 1 index block and 1 data block) in case of index access.

Well, that is not always true!

Let’s demonstrate …

SQL> sho parameter db_block_size

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
db_block_size integer 8192

SQL> sho parameter db_file_multiblock_read_count

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
db_file_multiblock_read_count integer 8

SQL> sho parameter optimizer_mode

NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
optimizer_mode string ALL_ROWS

- Create a table whose size = db_file_multiblock_read_count
i.e. = 8 blocks and has a unique index on id column

SQL> drop table mbrc_7b purge;
create table mbrc_7b (id number primary key, text char(700));

begin
for i in 1..60 loop
insert into mbrc_7b values (i, 'txt'||i);
commit;
end loop;
end;
/

exec dbms_stats.gather_table_stats(USER, 'MBRC_7B', cascade=> true);

– Check that table’s HWM is set to 7 blocks

select table_name, blocks from user_tables where table_name= 'MBRC_7B';

TABLE_NAME BLOCKS
—————————— ———-
MBRC_60K 7

– check that table has been assigned 8 blocks

SQL> select segment_name, min_extents, blocks, extents
from dba_segments
where segment_name like '%MBRC%';

SEGMENT_NA MIN_EXTENTS BLOCKS EXTENTS
———- ———– ———- ———-
MBRC_7B 1 8 1

- Note that the following query uses index access  although table size =db_file_multiblock_read_count and can be read in one multi block I/O

SQL> set autotrace traceonly
select * from mbrc_7b where id =1;
set autotrace off

Execution Plan
———————————————————-
Plan hash value: 4289258908

Execution Plan
———————————————————-
Plan hash value: 4289258908

——————————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
——————————————————————————————–
| 0 | SELECT STATEMENT | | 1 | 704 | 1 (0)| 00:00:09 |
| 1 | TABLE ACCESS BY INDEX ROWID| MBRC_7B | 1 | 704 | 1 (0)| 00:00:09 |
|* 2 | INDEX UNIQUE SCAN | SYS_C0014075 | 1 | | 0 (0)| 00:00:01 |

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

2 – access(“ID”=1)
Statistics
———————————————————-
0 recursive calls
0 db block gets
2 consistent gets
0 physical reads
0 redo size
1089 bytes sent via SQL*Net to client
405 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

- Let’s verify that the if FTS is used,  cost of the query is more (4) as compared to index access (1).

SQL>set autotrace traceonly
select /*+ full (s) */ * from mbrc_7b s where id =1;
set autotrace traceonly

Execution Plan
———————————————————-
Plan hash value: 1640139338

—————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
—————————————————————————–
| 0 | SELECT STATEMENT | | 1 | 704 | 4 (0)| 00:00:35 |
|* 1 | TABLE ACCESS FULL| MBRC_7B | 1 | 704 | 4 (0)| 00:00:35 |
—————————————————————————–

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

1 – filter(“ID”=1)
Statistics
———————————————————-
0 recursive calls
0 db block gets
10 consistent gets
0 physical reads
0 redo size
1179 bytes sent via SQL*Net to client
416 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
2
– we can see that cost of FTS is more than index access
The reason as explained by Oracle Guru Richard Foote is :

Oracle needs to read the table segment header in order to determine what blocks need to be accessed to begin the FTS operation.

Oracle begins a FTS function call by accessing the segment header of the segment as it contains vital info required for the FTS operation.

However an index scan (but not a Fast Full Index Scan) begins by directly accessing the index root block (or single leaf block if it’s a level 0 index). there is nothing within the index segment header that’s required for an index scan, so the segment header is an “overhead” we can save and potentially taken advantage of.

Oracle must visit the table segment header during a FTS because it contains vital information necessary to perform the FTS, namely the extent map and the High Water Mark (HWM) associated with the table.

During an index scan operation, there’s nothing of interest within the index segment header. The critical index block, the index block by which all index scans must start is the root block of the index (except Fast Full Index Scans which are basically the FTS equivalent for indexes). There’s no need to access the index segment header because it’s the root block that actually contains all the necessary information by which to start the index scan operation. The root blocks contains the pointers to subsequent index blocks (be it a branch or leaf blocks) that Oracle needs to follow in order to find the index entry of interest.

with a Unique index, there can only be a maximum of 1 row returned. Therefore, when selecting this one row, Oracle doesn’t have to perform the second fetch operation to confirm there are indeed no more rows to return

– Let’s see what happens if we access all the rows with a predicate id between <min_val> and <max_val>

— Note that

  • Optimizer chooses FTS !!!
  • Cost of the query is 4 units i.e same as earlier with predicate where id=1
  • Consistent gets = 13
SQL> set autotrace traceonly
select * from mbrc_7b where id between 1 and 60;
set autotrace off
Execution Plan
----------------------------------------------------------
Plan hash value: 1640139338

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 60 | 42240 | 4 (0)| 00:00:35 |
|* 1 | TABLE ACCESS FULL| MBRC_7B | 60 | 42240 | 4 (0)| 00:00:35 |
-----------------------------------------------------------------------------

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

1 - filter("ID">=1 AND "ID"<=60)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
13 consistent gets
0 physical reads
0 redo size
43490 bytes sent via SQL*Net to client
449 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
60 rows processed

- Let’s verify that the cost of the query is more if index is used
– Note that index range scan has CPU cost of 1 as compared of 0 when index unique scan was used

– Cost of the query is 7 units as compared to 4 for FTS
– No. of consistent gets are 15 as compared to 13 in FTS

SQL>set autotrace traceonly
select /*+ index (s) */ * from mbrc_7b s where id between 1 and 60;
set autotrace off
Execution Plan
----------------------------------------------------------
Plan hash value: 1386006575

--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 60 | 42240 | 7 (0)| 00:01:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| MBRC_7B | 60 | 42240 | 7 (0)| 00:01:01 |
|* 2 | INDEX RANGE SCAN | SYS_C0014075 | 60 | | 1 (0)| 00:00:09 |
--------------------------------------------------------------------------------------------

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

2 - access("ID">=1 AND "ID"<=60)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
15 consistent gets
0 physical reads
0 redo size
43490 bytes sent via SQL*Net to client
449 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
60 rows processed

*****************************************

Hence, it can be seen that whereas cost of FTS remains at 4 irrespective of the predicate, cost of index access increases from 1 to 7 as no. of data blocks accessed increase.

Logically speaking, there should be a threshold no. of data blocks accessed above which the execution plan changes from Index access to FTS.  After trying various ranges, I found out that plan switches from index access to FTS as id range changes from 1 – 29 to 1 – 30.

(Please note that above values might vary from  system to system  due to the difference in system statistics. )

Let’s verify…

— Note that index access is used for id range 1 – 29

SQL> set autotrace traceonly

select * from mbrc_7b where id between 1 and 29;

set autotrace off

29 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 1386006575

-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 29 | 20416 | 4 (0)| 00:00:3
| 1 | TABLE ACCESS BY INDEX ROWID| MBRC_7B | 29 | 20416 | 4 (0)| 00:00:3
|* 2 | INDEX RANGE SCAN | SYS_C0014075 | 29 | | 1 (0)| 00:00:0
-----------------------------------------------------------------------------------------

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

2 - access("ID">=1 AND "ID"<=29)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
21251 bytes sent via SQL*Net to client
427 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
29 rows processed

– Note that plan changes to FTS as upper limit of the range changes to 30

SQL> set autotrace traceonly

select * from mbrc_7b where id between 1 and 30;

set autotrace off

30 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 1640139338

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 30 | 21120 | 4 (0)| 00:00:35 |
|* 1 | TABLE ACCESS FULL| MBRC_7B | 30 | 21120 | 4 (0)| 00:00:35 |
-----------------------------------------------------------------------------

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

1 - filter("ID"<=30 AND "ID">=1)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
21960 bytes sent via SQL*Net to client
427 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
30 rows processed

– Note that for  id between 1 and 30 indexed access costs 5 units whereas FTS cost is 4.

SQL> set autotrace traceonly
select /*+ index(s) */ * from mbrc_7b s where id between 1 and 30;
set autotrace off

Execution Plan
----------------------------------------------------------
Plan hash value: 1386006575

--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 30 | 21120 | 5 (0)| 00:00:44 |
| 1 | TABLE ACCESS BY INDEX ROWID| MBRC_7B | 30 | 21120 | 5 (0)| 00:00:44 |
|* 2 | INDEX RANGE SCAN | SYS_C0014075 | 30 | | 1 (0)| 00:00:09 |
--------------------------------------------------------------------------------------------

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

2 - access("ID">=1 AND "ID"<=30)
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
21960 bytes sent via SQL*Net to client
427 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
30 rows processed

— Let’s have a table with non unique index –

SQL> drop table mbrc_nu purge;
create table mbrc_nu (id number , text char(700));

begin
for i in 1..60 loop
for j in 1..10 loop
insert into mbrc_nu values (i, 'txt'||j);
commit;
end loop;
commit;
end loop;
end;
/

– Check that the table has 10 records for each value of id

select id, count(*) from mbrc_nu group by id order by id;
ID COUNT(*)
--------- ----------
1 10
2 10
3 10
4 10
...
59 10
60 10

– create non unique index on the table and gather statistics

create index mbrc_nu_idx on mbrc_nu(id);
exec dbms_stats.gather_table_stats(USER, 'MBRC_NU', cascade=> true);

select table_name, num_rows, blocks from user_tables where table_name= 'MBRC_NU';

TABLE_NAME NUM_ROWS BLOCKS
------------------------------ ---------- ----------
MBRC_NU 600 60
SQL> select segment_name, min_extents, blocks, extents
from dba_segments
where segment_name like '%MBRC_NU%';

SEGMENT_NAME MIN_EXTENTS BLOCKS EXTENTS
--------------- ----------- ---------- ----------
MBRC_NU 1 64 8
MBRC_NU_IDX 1 8 1

– Note that the index has a low clustering factor of 60 close to no. of  blocks (60) ) i.e rows for a key value are placed in the same block

SQL>col index_name for a15
Col table_name for a15
select index_name, table_name,blevel, leaf_blocks, clustering_factor
from user_indexes
where index_name = 'MBRC_NU_IDX';

INDEX_NAME TABLE_NAME BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR
--------------- --------------- ---------- ----------- -----------------
MBRC_NU_IDX MBRC_NU 1 2 60

– check that all 10 rows with id = 1 are in the same block

sql>select count(*) , id, dbms_rowid.rowid_block_number(rowid)
from mbrc_nu
where id = 1
group by id, dbms_rowid.rowid_block_number(rowid);

COUNT(*) ID DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
--------- ---------- ------------------------------------
10 1 100025

– Note that for predicate id = 1,  index is used as only one table block needs to be accessed

SQL> set autotrace traceonly
select * from mbrc_nu where id =1;
set autotrace off

Execution Plan
----------------------------------------------------------
Plan hash value: 1257710425

-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 7040 | 3 (0)| 00:00:26 |
| 1 | TABLE ACCESS BY INDEX ROWID| MBRC_NU | 10 | 7040 | 3 (0)| 00:00:26 |
|* 2 | INDEX RANGE SCAN | MBRC_NU_IDX | 10 | | 1 (0)| 00:00:09 |
-------------------------------------------------------------------------------------------

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

2 - access("ID"=1)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
5 consistent gets
0 physical reads
0 redo size
7654 bytes sent via SQL*Net to client
415 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
10 rows processed

* Let’s check cost of plan if FTS is used
* Note that cost of the plan is much higher (24) as compared to index scan(3)

SQL>set autotrace traceonly
select /*+ full(s) */ * from mbrc_nu  where id =1;
set autotrace off

Execution Plan
----------------------------------------------------------
Plan hash value: 112258944

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 7040 | 24 (0)| 00:03:27 |
|* 1 | TABLE ACCESS FULL| MBRC_NU | 10 | 7040 | 24 (0)| 00:03:27 |
-----------------------------------------------------------------------------

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

1 - filter("ID"=1)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
64 consistent gets
0 physical reads
0 redo size
7602 bytes sent via SQL*Net to client
415 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
10 rows processed

Just like in case of unique indexed table, here also execution plan will change from Index access to FTS when more than a threshold range of id’s are queried

Here the threshold is id = 1 to 21 i.e. whenever more than 21 table blocks need to be queried, FTS will be used.

Let’s verify
* Note that Index access is used for range 1 – 21
* Note that cost of index access is 24 which is same as FTS as seen earlier

SQL> set autotrace traceonly
select * from mbrc_nu where id between 1 and 21;
set autotrace off
Execution Plan
----------------------------------------------------------
Plan hash value: 1257710425

-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 213 | 146K| 24 (0)| 00:03:27 |
| 1 | TABLE ACCESS BY INDEX ROWID| MBRC_NU | 213 | 146K| 24 (0)| 00:03:27 |
|* 2 | INDEX RANGE SCAN | MBRC_NU_IDX | 213 | | 2 (0)| 00:00:18 |
-------------------------------------------------------------------------------------------

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

2 - access("ID">=1 AND "ID"<=21)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
77 consistent gets
0 physical reads
0 redo size
151313 bytes sent via SQL*Net to client
558 bytes received via SQL*Net from client
15 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
210 rows processed

* Let’s check for range 1 – 22
* Note that plan switches to FTS as from this range onwards
Cost of index access is more than FTS

SQL> set autotrace traceonly
select * from mbrc_nu where id between 1 and 22;
set autotrace off

Execution Plan
----------------------------------------------------------
Plan hash value: 112258944

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 224 | 154K| 24 (0)| 00:03:27 |
|* 1 | TABLE ACCESS FULL| MBRC_NU | 224 | 154K| 24 (0)| 00:03:27 |
-----------------------------------------------------------------------------

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

1 - filter("ID"<=22 AND "ID">=1)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
78 consistent gets
0 physical reads
0 redo size
158543 bytes sent via SQL*Net to client
569 bytes received via SQL*Net from client
16 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
220 rows processed

* Let’s verify that from this range onwards Cost of index access is more than FTS
* Note than cost of index access for id = 1 – 22  is 25 which is more than that of FTS (24)

SQL> set autotrace traceonly
select /*+ index (s) */ * from mbrc_nu s where id between 1 and 22;
set autotrace off

Execution Plan
----------------------------------------------------------
Plan hash value: 1257710425

-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 224 | 154K| 25 (0)| 00:03:36 |
| 1 | TABLE ACCESS BY INDEX ROWID| MBRC_NU | 224 | 154K| 25 (0)| 00:03:36 |
|* 2 | INDEX RANGE SCAN | MBRC_NU_IDX | 224 | | 2 (0)| 00:00:18 |
-------------------------------------------------------------------------------------------

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

2 - access("ID">=1 AND "ID"<=22)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
54 consistent gets
0 physical reads
0 redo size
158543 bytes sent via SQL*Net to client
569 bytes received via SQL*Net from client
16 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
220 rows processed

Hence, it can be concluded that as no. of blocks accessed increase, cost of FTS remains same but cost of index access increases. The switching of plan from Index access to FTS takes place after a thershold cost of index access becomes more than FTS.

In case of non unique index, we need to consider another factor i.e clustering factor of index. In the scenario above, clustering factor of the index was low (approached no. of blocks) and all the rows belonging to same id were placed in the same block and hence only one table block needed to be accessed for each value of id. But if clustering factor of the index is high (approaches no. of rows), rows of an id are scattered across a large no. of blocks and cost of index access increases. In that case switching from index access to FTS might occur at a smaller threshold.

Let’s verify

- create another table mbrc_hi_cf from mbrc_nu with rows ordered randomly

sql>drop table mbrc_hi_cf purge;
create table mbrc_hi_cf as
select * from
(select * from mbrc_nu order by dbms_random.random)
order by dbms_random.random;

create index mbrc_hi_cf_idx on mbrc_hi_cf(id);

exec dbms_stats.gather_table_stats(USER, 'MBRC_HI_CF', cascade=> true);

select table_name, num_rows, blocks from user_tables where table_name= 'MBRC_HI_CF';

TABLE_NAME NUM_ROWS BLOCKS
------------------------------ ---------- ----------
MBRC_NU 600 60

SQL>
SQL> col segment_name for a20
select segment_name, min_extents, blocks, extents
from dba_segments
where segment_name like '%MBRC_HI_CF%';

SEGMENT_NAME MIN_EXTENTS BLOCKS EXTENTS
-------------------- ----------- ---------- ----------
MBRC_HI_CF 1 64 8
MBRC_HI_CF_IDX 1 8 1

– Note that the index has a high clustering factor of 565 close to rows (600) )i.e rows for a key value are scattered acorss different blocks

SQL>col index_name for a15
Col table_name for a15
select index_name, table_name,blevel, leaf_blocks, clustering_factor
from user_indexes
where index_name = 'MBRC_HI_CF_IDX';
INDEX_NAME TABLE_NAME BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR
--------------- --------------- ---------- ----------- -----------------
MBRC_HI_CF_IDX MBRC_HI_CF 1 2 565

— check that 10 rows with id = 1 are scattered across 10 blocks

sql>select count(*) , id, dbms_rowid.rowid_block_number(rowid)
from mbrc_hi_cf
where id = 1
group by id, dbms_rowid.rowid_block_number(rowid);

COUNT(*) ID DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
---------- ---------- ------------------------------------
1 1 109200
1 1 109227
1 1 109231
1 1 109186
1 1 109203
1 1 109210
1 1 109188
1 1 100092
1 1 109196
1 1 109234

10 rows selected.

– Let’s check the plan used by optimizer for id = 1
— Note that it still uses index access but cost has increased from 3 (in mbrc_nu) to 11 as 10 data blocks are accessed to get all the rows

SQL> set autotrace traceonly
select * from mbrc_hi_cf where id =1;
set autotrace off

Execution Plan
----------------------------------------------------------
Plan hash value: 3955578425

----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 7040 | 11 (0)| 00:01:35 |
| 1 | TABLE ACCESS BY INDEX ROWID| MBRC_HI_CF | 10 | 7040 | 11 (0)| 00:01:35 |
|* 2 | INDEX RANGE SCAN | MBRC_HI_CF_IDX | 10 | | 1 (0)| 00:00:09 |
----------------------------------------------------------------------------------------------

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

2 - access("ID"=1)
Statistics
----------------------------------------------------------
24 recursive calls
0 db block gets
15 consistent gets
0 physical reads
0 redo size
7654 bytes sent via SQL*Net to client
415 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
10 rows processed

* Let’s check cost of plan if FTS is used
* Note that cost of the plan is same (24) as earlier (mbrc_nu) and is more than
that of index access (11)

SQL>set autotrace traceonly
select /*+ full(s) */ * from mbrc_hi_cf s where id =1;
set autotrace off

Execution Plan
----------------------------------------------------------
Plan hash value: 1581728470

--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 7040 | 24 (0)| 00:03:27 |
|* 1 | TABLE ACCESS FULL| MBRC_HI_CF | 10 | 7040 | 24 (0)| 00:03:27 |
--------------------------------------------------------------------------------

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

1 - filter("ID"=1)
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
64 consistent gets
0 physical reads
0 redo size
7602 bytes sent via SQL*Net to client
415 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
10 rows processed

Just like in case of unique indexed table, here also execution plan will change from Index access to FTS when more than a threshold range of id’s are queried.

Here the  threshold is reached for a much smaller range of id’s i.e for id = 1 to 2 , index access is used but plan switches to FTS  for id = 1 to 3 and above as rows for same id are scattered across multiple blocks.

– Let’s verify..

* Note that Index access is used for range 1 – 2
* Note that cost of index access is 21 which is less than FTS (24) as seen earlier

SQL> set autotrace traceonly
select * from mbrc_hi_cf where id between 1 and 2;
set autotrace off
Execution Plan
----------------------------------------------------------
Plan hash value: 3955578425

----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 20 | 14080 | 21 (0)| 00:03:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| MBRC_HI_CF | 20 | 14080 | 21 (0)| 00:03:01 |
|* 2 | INDEX RANGE SCAN | MBRC_HI_CF_IDX | 20 | | 2 (0)| 00:00:18 |
----------------------------------------------------------------------------------------------

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

2 - access("ID">=1 AND "ID"<=2)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
65 consistent gets
0 physical reads
0 redo size
14142 bytes sent via SQL*Net to client
426 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
20 rows processed


— Let’s verify that plan switches from index access to FTS for the range id = 1 – 3

SQL> set autotrace traceonly
select * from mbrc_hi_cf where id between 1 and 3;
set autotrace off

Execution Plan
----------------------------------------------------------
Plan hash value: 1581728470

--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 30 | 21120 | 24 (0)| 00:03:27 |
|* 1 | TABLE ACCESS FULL| MBRC_HI_CF | 30 | 21120 | 24 (0)| 00:03:27 |
--------------------------------------------------------------------------------

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

1 - filter("ID"<=3 AND "ID">=1)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
65 consistent gets
0 physical reads
0 redo size
19859 bytes sent via SQL*Net to client
426 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
30 rows processed

SQL> set autotrace off

– Let’s verify that from this range onwards Cost of index access is more than FTS as records of each id are scattered across a large no.. of blocks
— Note than cost of index access is 31 which is more than that of FTS (24)

SQL> set autotrace traceonly
select /*+ index (s) */ * from mbrc_hi_cf s where id between 1 and 3;
set autotrace off

Execution Plan
----------------------------------------------------------
Plan hash value: 3955578425

----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 30 | 21120 | 31 (0)| 00:04:27 |
| 1 | TABLE ACCESS BY INDEX ROWID| MBRC_HI_CF | 30 | 21120 | 31 (0)| 00:04:27 |
|* 2 | INDEX RANGE SCAN | MBRC_HI_CF_IDX | 30 | | 2 (0)| 00:00:18 |
----------------------------------------------------------------------------------------------

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

2 - access("ID">=1 AND "ID"<=3)
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
32 consistent gets
0 physical reads
0 redo size
21230 bytes sent via SQL*Net to client
426 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
30 rows processed

Hence it can be concluded that in case we have multiple rows with same id’s , FTS may be used for a smaller range of id’s if clustering factor is high.

Summary:

– For tables sized smaller than db_file-multiblock_read_count, index access may be preferred to FTS as no. of blocks accessed in FTS are more than those accessed in index access.

– As no. of table blocks accessed increase, cost of FTS remains same but cost of index access increases. The execution plan changes from index access to FTS after a threshold no. of table blocks accessed.

– For tables with non unique index, the clustering factor also plays a role in determining the threshold id range for plan switching. The switching takes place at a smaller range if clustering factor is high and vice versa.

I hope this post was useful.

Thanks for your time!

References:

https://forums.oracle.com/forums/thread.jspa?messageID=11051591#11051591
http://richardfoote.wordpress.com/2009/04/16/indexes-on-small-tables-part-i-one-of-the-few/
——————————————————————————————–

Related links:

Home
Tuning Index
Clustering Factor Demystified : Part-I
Clustering Factor Demystified : Part-II
Clustering Factor Demystified : Part-III
DB_FILE_MULTIBLOCK_READ_COUNT And Extent Size
Multiblock Reads and Cached Blocks

 

IDENTIFY THE DATABASE OWNING A CPU INTENSIVE PROCESS

On a linux/Unix server with multiple database instances running , if one of the databases has a process which is CPU intensive, we want to  identify the database which is the owner of  that process.

Currently, I have two databases – AMER and EURO running on the same server.
To simulate the environment, I needed to run a process in a database which consumes a lot of CPU. Since hash joins consume CPU, I wrote the following query and checked its explain plan

SQL> explain plan for
sELECT /*+ USE_HASH(l h) */ * FROM oe.orders h,oe.order_items l WHERE l.order_id = h.order_id;

select * from table(dbms_xplan.display);<

PLAN_TABLE_OUTPUT
————————————————————————————-
Plan hash value: 864676608

———————————————————————————-
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
———————————————————————————-
| 0 | SELECT STATEMENT | | 665 | 36575 | 409 (1)| 00:00:01 |
|* 1 | HASH JOIN | | 665 | 36575 | 409 (1)| 00:00:01 |
| 2 | TABLE ACCESS FULL| ORDERS | 105 | 3885 | 204 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| ORDER_ITEMS | 665 | 11970 | 204 (0)| 00:00:01 |
———————————————————————

The explain clearly shows that hash join will be used. I executed this query repeatedly against EURO database using the following script :

SQL>conn / as sysdba

begin
for i in 1..100000000 loop
execute immediate 'sELECT /*+ USE_HASH(l h) */ * FROM oe.orders h,oe.order_items l WHERE l.order_id = h.order_id';
end loop;
end;
/

In another terminal, I issued “top” linux command to find out top CPU consuming processes. This is the output I got

 [root@node1 ~]# top

top – 21:54:39 up 2 days, 22:08, 6 users, load average: 4.75, 4.29, 4.16
Tasks: 244 total, 4 running, 240 sleeping, 0 stopped, 0 zombie
Cpu(s): 44.6%us, 44.9%sy, 0.0%ni, 0.0%id, 0.0%wa, 9.8%hi, 0.7%si, 0.0%st
Mem: 1667320k total, 1654112k used, 13208k free, 130188k buffers
Swap: 8193140k total, 643728k used, 7549412k free, 1133656k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
14305 oracle 20 0 632m 66m 55m R 26.1 4.1 0:03.25 oracle
14665 root 20 0 22772 1548 1216 R 23.8 0.1 0:42.47 perl
21119 oracle 20 0 194m 3152 1500 D 15.2 0.2 164:15.69 mgr
142 root 20 0 0 0 0 R 13.5 0.0 132:57.93 kjournald
19272 oracle 20 0 166m 2292 1700 S 6.3 0.1 52:30.99 extract
3685 root 20 0 5924 544 488 D 5.6 0.0 52:14.51 syslogd

Here, It can be seen that the process with PID 14305 owned by oracle user is consuming most of the CPU.
But we don’t know which database does this process belong to. Let’s try to see the status of process with PID 14305

#[root@node1 ~]# ps -p 14305 -f

UID PID PPID C STIME TTY TIME CMD
oracle 14305 9918 2 21:44 ? 00:00:18 oracleeuro (DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))

It can be seen that process is running against euro database. Since local connection was made to the database, LOCAL= YES is present in the connection string.

— Now I will run the same script after making remote connection to euro database.

SQL>conn sys/oracle@euro as sysdba

begin
for i in 1..100000000 loop
execute immediate 'sELECT /*+ USE_HASH(l h) */ * FROM oe.orders h,oe.order_items l WHERE l.order_id = h.order_id';
end loop;
end;
/

In another terminal, I issued “top” linux command to find out top CPU consuming processes.

[root@node1 ~]# top

This is the output I got :
top – 22:35:47 up 2 days, 22:49, 6 users, load average: 4.24, 3.16, 3.34
Tasks: 236 total, 4 running, 232 sleeping, 0 stopped, 0 zombie
Cpu(s): 34.4%us, 46.0%sy, 0.0%ni, 0.0%id, 0.0%wa, 17.5%hi, 2.1%si, 0.0%st
Mem: 1667320k total, 1650820k used, 16500k free, 132964k buffers
Swap: 8193140k total, 604000k used, 7589140k free, 1128896k cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
16789 oracle 20 0 626m 19m 17m R 32.6 1.2 0:39.00 oracle
21119 oracle 20 0 194m 3152 1500 R 23.4 0.2 172:30.19 mgr
142 root 20 0 0 0 0 R 21.3 0.0 140:19.24 kjournald
3685 root 20 0 5924 544 488 D 8.6 0.0 55:11.53 syslogd
19272 oracle 20 0 166m 2292 1700 S 8.3 0.1 55:32.88 extract
4704 root 20 0 109m 10m 3720 S 1.5 0.6 3:54.93 Xorg
7503 oracle 20 0 284m 11m 4432 S 1.2 0.7 1:09.44 gnome-terminal
4909 oracle 20 0 268m 2628 2328 S 0.6 0.2 15:52.64 ocssd.bin
4535 root 19 -1 112m 608 520 S 0.3 0.0 0:23.08 modclusterd

[root@node1 ~]# ps -p 16789 -f

UID PID PPID C STIME TTY TIME CMD
oracle 16789 1 33 22:33 ? 00:00:54 oracleeuro (LOCAL=NO)

Here again, It can be seen that process is running against euro database. Since remote connection was made, LOCAL=NO can be seen in the connection string

I hope this post was useful. Your comments and sugegstions are always welcome!

————————————————————————————————-

Home

Tuning Index

 

——————

CLUSTERING FACTOR DEMYSTIFIED : PART – III

 

How to resolve the performance issues due to high clustering factor?

In my earlier post, Clustering Factor Demystified : Part – I, I had discussed that to improve the Clustering Factor,  the table must be rebuilt (and reordered). The data retrieval can be considerably speeded up by physically sequencing the rows in the same order as the key column. If we can group together the rows for a key value,  we can get all of the row with a single block read because the rows are together.  To achieve this goal, various methods may be used. In the post Clustering Factor Demystified : Part -II, I had demonstrated Manual Row Re-sequenciung (CTAS with order by) which pre-orders data to avoid expensive disk sorts after retrieval. In this post, I will demonstrate the use of  Single table hash clusters  and  Single table index clusters which  clusters related rows together onto the same data block .

Overview: 

  •  Create a table organized which contains two columns  -  id(number) and txt (char)- Populate the table insert 34 records for each value of id where id ranges from 1 to 100- In this case as records are added sequentially,  records for a key value are stored together
  •  Create another table unorganized which is a replica of the ‘organized’ table but records are inserted in a random manner so that records for a key value may be scattered across different blocks .
  •  Create a single table index cluster  table from ‘unorganized’ table using CTAS.
  •  Create a single table hash cluster table from ‘unorganized’  table using CTAS
  •  Trace  the query using exact match  on three tables and verify that hash cluster table gives the best performance .
  •  Trace the query using range scan  on three tables and verify that index cluster table gives the best performance .
  •  Verify that index and hash cluster tables have better clustering factor .
 Implementation:
- Create a table organized which contains two columns  –  id(number) and txt (char)
– Populate the table insert 34 records for each value of id where id ranges from 1 to 100
– In this case as records are added sequentially,  records for a key value are stored together
SQL> drop table organized purge;
           create table organized (id number(3), txt char(900));
           begin
           for i in 1..100 loop
               insert into organized select i, lpad(‘x’, 900, ‘x’)
               from    dba_objects where rownum < 35;
           end loop;
          end;
          /
-  create another table unorganized which is a replica of the ‘organized’ table but records are inserted in a random manner so that records for a key value may be scattered across different blocks (order by dbms_random.random).
SQL> drop table unorganized purge;
           create table unorganized as select * from organized order by dbms_random.random;

           create index unorganized_idx on unorganized(id);

        exec dbms_stats.gather_table_stats(USER, ‘unorganized’, estimate_percent => 100, method_opt=> ‘for all indexed columns size 254′);

– Create a single table index cluster  table from ‘unorganized’ table using CTAS.

— Create a cluster with size = blocksize = 8k and index it

SQL> drop cluster index_cluster including tables;
           create cluster index_cluster
            ( id number(3) )
          size 8192;


          create index index_cluster_idx
           on cluster index_cluster;
        drop table index_cluster_tab purge;       
       create table index_cluster_tab 
           cluster index_cluster( id )
           as select * from unorganized ;
 – Create a single table hash cluster table from ‘unorganized’  table using CTAS 
 SQL>drop tablespace mssm including contents and datafiles;  
        Create tablespace mssm datafile ‘/u01/app/oracle/oradata/orcl/mssm01.dbf’ size 100m     segment space management manual;   
         drop cluster hash_cluster including tables;
           create cluster Hash_cluster
           ( id number(3) )
             size 8192 single table hash is id hashkeys 100 tablespace mssm; 
        drop table hash_cluster_tab purge;     
        create table hash_cluster_tab          cluster hash_cluster(id)
           as select * from unorganized;
begin
    dbms_stats.gather_table_stats
    ( user, ‘UNORGANIZED’, cascade=>true );    dbms_stats.gather_table_stats
    ( user, ‘INDEX_CLUSTER_TAB’, cascade=>true );    dbms_stats.gather_table_stats
    ( user, ‘HASH_CLUSTER_TAB’, cascade=>true );
End;
/
- Find out no. of blocks across which records of a key value are spread in the two tables.- Note that in ‘unorganized’  table,  records  for an id are scattered across more than 30 blocks whereas in index_cluster_tab and hash_cluster_tab tables, records for each id are clustered i.e. records for each key value are spread across 5 blocks only.
SQL> select unorg.id id, unorg.cnt unorganized_blocks,
           idx_tab.cnt index_cluster_blocks, hash_tab.cnt hash_cluster_blocks
         from
             ( select id,  count(distinct(dbms_rowid.ROWID_BLOCK_NUMBER(rowid))) cnt
             from unorganized     
              group by id) unorg,
           ( select id,  count(distinct(dbms_rowid.ROWID_BLOCK_NUMBER(rowid))) cnt
             from index_cluster_tab     
              group by id) idx_tab,
           ( select id,  count(distinct(dbms_rowid.ROWID_BLOCK_NUMBER(rowid))) cnt
             from hash_cluster_tab     
              group by id) hash_tab 
        where idx_tab.id = unorg.id
          and hash_tab.id = unorg.id
        order by id;
ID              UNORGANIZED_BLOCKS INDEX_CLUSTER_BLOCKS HASH_CLUSTER_BLOCKS
———-             ——————                 ——————–                                     ——————-

1                 33                    5                   5

2                 34                    5                   5

3                 33                    5                   5

4                 33                    5                   5

5                 33                    5                   5

6                 32                    5                   5

7                 34                    5                   5

8                 34                    5                   5

9                 33                    5                   5

10                 32                    5                   5

90                 34                    5                   5

91                 34                    5                   5

92                 30                    5                   5

93                 34                    5                   5

94                 34                    5                   5

95                 34                    5                   5

96                 34                    5                   5

97                 33                    5                   5

98                 33                    5                   5

99                 34                    5                   5

100               34                    5                   5

- Trace  the query using exact match  on three tables and verify that hash cluster table gives the best performance .
– Let’s compare the statistics when rows for all the id’s are retrieved in succession from the three tables
conn / as sysdba
alter session set tracefile_identifier = ‘cluster_factor';
alter session set sql_trace=true;
declare
    type tab_row is table of unorganized%rowtype;
    tab_rows tab_row;
    type id_val is table of unorganized.id%type;
    id_vals id_val;   
begin
    select distinct id bulk collect into id_vals
      from unorganized;   for k in id_vals.first .. id_vals.last
    loop
        select * bulk collect into tab_rows
          from unorganized
         where id = k;
    end loop;
end;
/
declare
    type tab_row is table of index_cluster_tab%rowtype;
    tab_rows tab_row;
    type id_val is table of index_cluster_tab.id%type;
    id_vals id_val;    begin
    select distinct id bulk collect into id_vals
      from index_cluster_tab;    for k in id_vals.first .. id_vals.last
    loop
        select * bulk collect into tab_rows
          from index_cluster_tab
         where id = k;
    end loop;
end;
/declare
    type tab_row is table of hash_cluster_tab%rowtype;
    tab_rows tab_row;
    type id_val is table of hash_cluster_tab.id%type;
    id_vals id_val;    begin
    select distinct id bulk collect into id_vals
      from hash_cluster_tab;    for k in id_vals.first .. id_vals.last
    loop
        select * bulk collect into tab_rows
          from hash_cluster_tab
         where id = k;
    end loop;
end;
/
– Find out the name of trace file generatedSQL> col trace_file for a100
           select  value trace_file from v$diag_info
           where upper(name) like ‘%TRACE FILE%';
TRACE_FILE
—————————————————————————————————
/u01/app/oracle/diag/rdbms/orcl1/orcl1/trace/orcl1_ora_24963_cluster_factor.trc– Run tkprof utility on the trace file generated $cd /u01/app/oracle/diag/rdbms/orcl/orcl/trace
   rm cluster_factor.out   tkprof /u01/app/oracle/diag/rdbms/orcl1/orcl1/trace/orcl1_ora_8403_cluster_factor.trc cluster_factor.out

   vi cluster_factor.out

********************************************************************************
Here are the contents of the trace file:

In case of unorganized table, it can be seen that no. of blocks visited (3517) is approaches the number of rows (3400) in the table as rows for an id  are scattered across a large no. of blocks.

SQL ID: 0npa78p7jkfa5

Plan Hash: 1120857569

SELECT *
FROM
UNORGANIZED WHERE ID = :B1

call     count       cpu    elapsed       disk      query    current        rows
——- ——  ——– ———- ———- ———- ———-  ———-
Parse        1      0.00       0.00          0          0          0           0
Execute    100      0.00       0.00          0          0          0           0
Fetch      100      0.01       0.02          0       3517          0        3400
——- ——  ——– ———- ———- ———- ———-  ———-
total      201      0.01       0.02          0       3517          0        3400

*******************************************************************************
In case of single table index cluster, 
Total  I/O’s  = I/O’s against the table + I/O’s against the table

i/O’s against the table = no. of table blocks across which various records for different id’s are stored

Since we saw earlier that records for each key value are scattered across 5 blocks,

I/O’s against the table =  no. of distinct key values (id’s) * no. of blocks occupied by records for an id
= 100 * 5
= 500

Rest 100 I/O’s are made against against the index ( one I/O for each key value)

Hence total I/O’s = 100 + 500 = 600
********************************************************************************

SQL ID: 6qy378ww4729s
Plan Hash: 3651720007
SELECT *
FROM
INDEX_CLUSTER_TAB WHERE ID = :B1

call     count       cpu    elapsed       disk      query    current        rows
——- ——  ——– ———- ———- ———- ———-  ———-
Parse        1      0.00       0.00          0          0          0           0
Execute    100      0.00       0.00          0          0          0           0
Fetch      100      0.00       0.00          0        600          0        3400
——- ——  ——– ———- ———- ———- ———-  ———-
total      201      0.00       0.00          0        600          0        3400

*******************************************************************************
In case of single table hash cluster, as index access is not needed,
Total  I/O’s  = I/O’s against the table
=  no. of distinct key values (id’s) * no. of blocks occupied by records for an id
= 100 * 5
= 500
********************************************************************************

SQL ID: ctnu91v20p2x2

Plan Hash: 3860562250

SELECT *
FROM
HASH_CLUSTER_TAB WHERE ID = :B1

call     count       cpu    elapsed       disk      query    current        rows
——- ——  ——– ———- ———- ———- ———-  ———-
Parse        1      0.00       0.00          0          0          0           0
Execute    100      0.00       0.00          0          0          0           0
Fetch      100      0.00       0.00          0        500          0        3400
——- ——  ——– ———- ———- ———- ———-  ———-
total      201      0.00       0.00          0        500          0        3400

*******************************************************************************

Summarizing the above results :

unorganized          index_cluster_tab      hash_cluster_tab
total  CPU       0.01                             0.00                   0.00

elapsed            0.01                              0.02                   0.00
time

I/O’s               3517                              600                    500

Hence, it can be concluded that for exact match queries hash clusters give the best performance since least no. of I/O’s are made.

- Trace the query using range scan  on three tables and verify that index cluster table gives the best performance .

– Let’s compare the statistics when rows for entire range of id’s are retrieved from the three tables

conn / as sysdba
alter session set
tracefile_identifier = ‘cluster_factor';
alter session set
sql_trace=true;
declare
    type tab_row is table of
unorganized%rowtype;
    tab_rows tab_row;
    type id_val is table of
unorganized.id%type;
    id_vals id_val;
    begin
    select distinct id bulk collect into id_vals
     from unorganized;
    for j in id_vals.first..id_vals.first loop
     for k in id_vals.last .. id_vals.last loop
       select * bulk collect into tab_rows
       from unorganized
       where id >= j and id <= k;
     end loop;
    end loop;
end;
/
  
declare
    type tab_row is table of index_cluster_tab%rowtype;
    tab_rows tab_row;
    type id_val is table of index_cluster_tab.id%type;
    id_vals id_val;
    begin
    select distinct id bulk collect into id_vals
    from index_cluster_tab;
  
    for j in id_vals.first..id_vals.first loop
     for k in id_vals.last .. id_vals.last loop
        select * bulk collect into tab_rows
          from index_cluster_tab   
         where id >= j and id <= k;
      end loop;
    end loop;
end;
/
declare
    type tab_row is table of hash_cluster_tab%rowtype;
    tab_rows tab_row;
    type id_val is table of hash_cluster_tab.id%type;
    id_vals id_val;
    begin
    select distinct id bulk collect into id_vals
      from hash_cluster_tab;
    for j in id_vals.first..id_vals.first loop
     for k in id_vals.last .. id_vals.last loop
        select * bulk collect into tab_rows
          from hash_cluster_tab
      where id >= j and id <= k;
     end loop;
    end loop;
end;
/
  
– Find out the name of trace file generated
 SQL> col
trace_file for a100
           select  value trace_file from v$diag_info
           where upper(name) like ‘%TRACE
FILE%';
TRACE_FILE
—————————————————————————————————-
/u01/app/oracle/diag/rdbms/orcl1/orcl1/trace/orcl1_ora_24963_cluster_factor.trc
– Run tkprof utility on the trace file generated
$cd /u01/app/oracle/diag/rdbms/orcl/orcl/trace
   rm cluster_factor.out
   tkprof /u01/app/oracle/diag/rdbms/orcl1/orcl1/trace  /orcl1_ora_8403_cluster_factor.trc  cluster_factor.out
   vi cluster_factor.out
 Here are the contents of the trace file:

In case of unorganized table, it can be seen that Full table scan is done and

total I/O’s = Physical I/O’s + logical I/O’s

= 486 + 489 = 975

CPU usage = 0.01

elapsed time = 0.01
cost             = 127
********************************************************************************
SQL ID:
dpg9s5v7jannv
Plan Hash:
3859503019
SELECT *
FROM
 UNORGANIZED WHERE ID >= :B2 AND ID <=
: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.01       0.01        486        489          0        3400
——-
——  ——– ———- ———-
———- ———-  ———-
total        3
0.01       0.01        486        489          0        3400
Rows     Row Source Operation
——-
—————————————————
   3400
FILTER  (cr=489 pr=486 pw=0
time=3524 us)
   3400
TABLE ACCESS FULL UNORGANIZED (cr=489 pr=486 pw=0 time=1888 us cost=127
size=3073600 card=3400)
********************************************************************************
 In case of index cluster table, it can be seen that index range scan is performed on cluster index followed by table access cluster.

total I/O’s = Physical I/O’s + logical I/O’s

= 385 + (501 + 1)

= 887

CPU usage = 0.01

elapsed time = 0.01
cost             =  101
*******************************************************************************
SQL ID:
22k91ut1b18nj
Plan Hash:
533030663
SELECT *
FROM
 INDEX_CLUSTER_TAB WHERE ID >= :B2 AND ID
<= :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.01       0.01        385        501          0        3400
——-
——  ——– ———- ———-
———- ———-  ———-
total        3      0.01
0.01        385        501          0        3400
Misses in library
cache during parse: 1
Optimizer mode:
ALL_ROWS
Parsing user id:
SYS   (recursive depth: 1)
Rows     Row Source Operation
——-  —————————————————
   3400
FILTER  (cr=501 pr=385 pw=0
time=9441 us)
   3400
TABLE ACCESS CLUSTER INDEX_CLUSTER_TAB (cr=501 pr=385 pw=0 time=7679 us
cost=101 size=3073600 card=3400)
    100
INDEX RANGE SCAN INDEX_CLUSTER_IDX (cr=1 pr=0 pw=0 time=99 us cost=1
size=0 card=1)(object id 80458)
********************************************************************************
 In case of hash cluster table, it can be seen that hash access is made to the table.

total I/O’s = Physical I/O’s + logical I/O’s

= 503 + 506

= 1009

CPU usage = 0.04

elapsed time = 0.04
cost = 132
*******************************************************************************
 SQL ID:
c2www0m7npkqp
Plan Hash:
4115468836
SELECT *
FROM
 HASH_CLUSTER_TAB WHERE ID >= :B2 AND ID
<= :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.04       0.04        503        506          0        3400
——-
——  ——– ———- ———-
———- ———-  ———-
total        3
0.04       0.04        503        506          0        3400
Misses in library
cache during parse: 1
Optimizer mode:
ALL_ROWS
Parsing user id:
SYS   (recursive depth: 1)
Rows     Row Source Operation
——-
—————————————————
   3400
FILTER  (cr=506 pr=503 pw=0
time=39906 us)
   3400
TABLE ACCESS FULL HASH_CLUSTER_TAB (cr=506 pr=503 pw=0 time=37389 us
cost=132 size=3073600 card=3400)
********************************************************************************
 Summarizing above results :
                                unorganized          index_cluster_tab      hash_cluster_tab

total I/O’s                      975                     887                        1009

CPU usage                    0.01                    0.01                        0.04

elapsed time                  0.01                     0.01                        0.04
cost                              127                      101                         132
 
Hence, to search a range of values,  single table index cluster is the best choice.
CPU usage, I/O’s  and cost is the maximum in case of single table hash cluster table.
 
- Verify that index and hash cluster tables have better clustering factor .

Let’s compare clustering factor of indexes on the three tables.

Tables unorganized and index_cluster_tab already have index.

– Let’s create index on hash_cluster_tab and gather statistics .

SQL>create index hash_cluster_idx on hash_cluster_tab(id);

          exec dbms_stats.gather_index_stats(USER, ‘HASH_CLUSTER_IDX’);

          exec dbms_stats.gather_index_stats(USER, ‘INDEX_CLUSTER_IDX’);   

– Find out clustering factor of the three tables.

SQL> select index_name, clustering_factor

           from  user_indexes

          where index_name in (‘UNORGANIZED_IDX’, ‘INDEX_CLUSTER_IDX’, ‘HASH_CLUSTER_IDX’);

INDEX_NAME                     CLUSTERING_FACTOR

—————————— —————–

HASH_CLUSTER_IDX                             500

INDEX_CLUSTER_IDX                            100

UNORGANIZED_IDX                             3311

– Note that

– clustering factor of index on unorganized table approaches no. of rows in the table (3400).

– clustering factor of index on hash_cluster_tab table  = 500 . As entries for each id are spread across 5 blocks, 500 blocks need to be accessed to get all the rows and index is aware of this information.

– clustering factor of index on index_cluster_tab table = 100 as there are 100 entries (one for each id) in the index. Here also 500 table blocks need to be accessed to get all the rows but index contains information about only the first(or may be  the last) data block for an id. Rest 4 blocks containing records for that id are chained to it and index does not have that information and clustering factor of an index is computed on the basis of the information available in the index. That’s why clustering factor in this case = no. of index entries.

SUMMARY:
  •  Clustered tables cannot be truncated.
  •  Choosing the Key :Choosing the correct cluster key is dependent on the most common types of queries issued against the clustered tables. The cluster key should be on the column against which queries are most commonly issued.
 
HASH CLUSTERS
A hash cluster stores related rows together in the same data blocks. Rows in a hash cluster are stored together based on their hash value.
  • – Hash clusters are a great way to reduce IO on some tables, but they have their downside.

   *If too little space is reserved for each
key (small SIZE value)
, or if the cluster is created with too few hash keys (small HASHKEYS), then each key will split across multiple blocks negating the benefits of the cluster.When creating a hash cluster, it is important to choose the cluster key correctly and set the HASH IS, SIZE, and HASHKEYS parameters so that performance and space use are optimal.

   * If too much space is reserved for each key (large SIZE value), or if the cluster is created with too many hash keys (large
HASHKEYS), then the cluster will contain thousands of empty blocks that slow down full table scans .
 A SIZE value much larger results in wasted space.

  •  Hash clusters reduce contention and I/O since index is not accessed .When you use an index range scan + table access by index rowid, the root index block becomes  a “hot block” causing  contention for the cache buffers chains (cbc) latch and hence an increase in CPU usage.
  • Hashing works best for exact match searches i.e. SELECT … WHERE cluster_key = …;  

              A  properly sized hash cluster for a lookup table gives pretty much a SINGLE IO for a keyed lookup.

  •  Hash clusters should only really be used for tables which are static in size so that you can determine the number of rows and amount of space required for the tables in the cluster. If tables in a hash cluster require more space than the initial allocation for the cluster, performance degradation can be substantial because overflow blocks are required.
  • Hash clusters should only really be used for tables which have mostly read-only data. The hash  cluster will take marginally longer
    to insert into since the data now has a “place” to go and maintaining this structure will take longer then maintaining a HEAP table .Updates do not provide much overhead unless the hashkey is being updated.
  •  Hash clusters should not be used in  applications where most queries on the table retrieve rows over a range of cluster key values where a hash function cannot be used to determine the location of specific hash keys and  instead, the equivalent of a full table scan must be done to fetch the rows for the query:
  •  Hash clusters should not be used in applications where hash key is updated. The hashing values can not be recalculated and thus serious overflow can result.
  •  Hash clusters should not be used for tables which are not static and continually growing. If a table grows without limit, the space required over the life of the table (its cluster) cannot be predetermined.
  •   Hash clusters should not be used for when you cannot afford to pre-allocate the space that the hash cluster will eventually
    need.
  •  Hash clusters allocate all the storage for all the hash buckets when the cluster is created, so they may waste space.
  •  Full scans on single table hash clusters will cost as much as they would in a heap table.
INDEX CLUSTERS
In an indexed cluster, Oracle stores together rows having the same cluster key value. Each distinct cluster key value is stored only once in each data block, regardless of the number of tables and rows in which it occurs. This saves disk space and improves performance for many operations.
  •   Index clusters should be used for the apllications where most queries on the table retrieve rows over a range of cluster key values. For example, in full table scans or queries such as the following:

            SELECT . . . WHERE cluster_key < . . . ;

  • With an index, key values are ordered in the index, so cluster key values that satisfy the WHERE clause of a query can be found with relatively few I/Os.
  •   Index clusters should be used for the tables which are not static, but instead are continually growing and the space required over the life of the table (its cluster) cannot be predetermined.
  •  Index clusters should be used for applications which frequently perform full-table scans on the table and the table is sparsely populated. A full-table scan in this situation takes longer under hashing.
  •  Cluster index has one entry per cluster key and not for each row. Therefore, the index is smaller and less costly to access for finding multiple rows.

 References:

————————————————————–

Related links:

Home

Tuning Index

Buffer Cache Wait Events

Clustering Factor Demystified Part-I 

Clustering Factor Demystified Part-II

Direct Read Enhancements in 11g

Single Table Index Clusters

SingleTable Hash Clusters 

SQL Profile Demystified : Part – I

SQL Profile Demystified Part – II

Undocumented Parameters in Oracle 11g

 

———————–

AUTOMATIC DEGREE OF PARALLELISM (DOP) – PART – II

    

 In my last article , I discussed Automatic DOP computation in Oracle 11g Release 2.  In this  article, I will demonstrate the oracle behaviour for various values of PARALLEL_DEGREE_POLICY i.e. Manual, Limited and Auto.
—–      PARALLEL_DEGREE_POLICY= MANUAL —————-
SQL>conn / as sysdba
          alter system set parallel_adaptive_multi_user=false;
          conn sh/sh
         alter session set parallel_degree_policy=manual;
         alter session set parallel_min_time_threshold=auto;
— CREATE TABLES —
— P5 – DICTIONARY DOP = 5
– PDEF – DICTIONARY DOP = DEFAULT
SQL>drop table p5 purge;
          create table p5 parallel 5
         as select * from sales
        where rownum < 5000;
SQL>drop table pdef purge;
   
         create table pdef parallel
        as select * from sales
        where rownum < 5000;
SQL> select table_name,degree
from user_tables
          where table_name in (‘P5′, ‘PDEF’);
TABLE_NAME                    DEGREE
———————————————————————-
P5                                             5
PDEF                                DEFAULT
– CHECK THAT PARALLELISM USED = DICTIONARY DOP IN BOTH THE TABLES –
SQL> select count(*) from p5;
 
          select * from v$pq_sesstat
          where statistic = ‘Allocation Height';
STATISTIC                     LAST_QUERY SESSION_TOTAL
—————————————- ————-
Allocation Height                      5            0
SQL> select  count(*) from pdef;
 
     select * from v$pq_sesstat
     where statistic = ‘Allocation Height';
STATISTIC                     LAST_QUERY SESSION_TOTAL
—————————————- ————-
Allocation Height                      2            0
– PRIOR TO 11G R2, TO DECIDE PARALLELISM, THE DBA COULD SPECIFY DIFFERENT
– DICTIONARY DOP’S FOR DIFFERENT OBJECTS
– TO OVERRIDE THE DICTIONARY DOP, HE COULD USE HINTS AT OBJECT LEVEL –
SQL> select /*+ parallel (p5 8) */ count(*) from p5;
 
     select * from v$pq_sesstat
     where statistic = ‘Allocation Height';
STATISTIC                     LAST_QUERY SESSION_TOTAL
—————————————- ————-
Allocation Height                      8            0
SQL> select /*+ parallel (pdef 8) */ count(*) from pdef;
 
     select * from v$pq_sesstat
     where statistic = ‘Allocation Height';
STATISTIC                     LAST_QUERY SESSION_TOTAL
—————————————- ————-
Allocation Height                      8            0
—–      PARALLEL_DEGREE_POLICY= LIMITED —————-
SQL> alter session set parallel_degree_policy=limited;
– CHECK THAT DOP IS NOT COMPUTED FOR TABLES WITH NON DEFAULT –
– DICTIONARY DOP – FOR TABLE P5, DOP USED = DICTIONARY DOP = 5
SQL> select count(*) from p5;
 
     select * from v$pq_sesstat
     where statistic = ‘Allocation Height';
STATISTIC                     LAST_QUERY SESSION_TOTAL
—————————————- ————-
Allocation Height                    5            0
– CHECK THAT DOP IS  COMPUTED FOR TABLES WITH DEFAULT –
– DICTIONARY DOP – FOR TABLE PDEF, DOP IS COMPUTED TO BE 0 I.E. PARALLELIZATION
– IS NOT REQUIRED CONSIDERING THE SMALL SIZE OF THE OBJECT
SQL> select  count(*) from pdef;
 
     select * from v$pq_sesstat
     where statistic = ‘Allocation Height';
STATISTIC                     LAST_QUERY SESSION_TOTAL
—————————————- ————-
Allocation Height                      0            0
—–      PARALLEL_DEGREE_POLICY= AUTO —————-
SQL> alter session set parallel_degree_policy=auto;
– EXECUTE QUERY ON P5
– NOTE THAT DOP USED = 0 I.E. QUERY IS EXECUTED SERIALLY
– BECAUSE WITH CURRENT SIZE OF THE TABLE, SERIAL EXECUTION
– CAN EXECUTE THE STATEMENT WITHIN THE TIME SPECIFIED BY
– PARALLEL_MIN_TIME_THRESHOLD

SQL> select count(*) from p5;

 
     select * from v$pq_sesstat
     where statistic = ‘Allocation Height';
STATISTIC                    LAST_QUERY SESSION_TOTAL
—————————————- ————-
Allocation Height                      0            0
– RECREATE THE TABLE WITH MORE ROWS SO THAT PARALLELIZATION
– IS NEEDED TO EXECUTE THE QUERY
SQL>drop table p5 purge;
    create table p5 parallel 5
    as select * from sh.sales;
– REEXECUTE THE QUERY AND CHECK THAT PARALLELIZATION
– IS USED AND DOP USED IS NOT 5 (DICTIONARY DOP) I.E. AUTOMATIC
– DOP IS COMPUTED EVEN IN CASE OF TABLE WHERE DICTIONARY DOP IS
– NOT DEFAULT
     select count(*) from p5;
 
     select * from v$pq_sesstat
     where statistic = ‘Allocation Height';
STATISTIC                     LAST_QUERY SESSION_TOTAL
—————————————- ————-
Allocation Height                      2            0
– MODIFY
PARALLEL_MIN_TIME_THRESHOLD TO 20 SECS –
SQL>alter session set parallel_min_time_threshold=20;
EXECUTE THE SAME QUERY AGAIN. CHECK THAT THE QUERY
– IS EXECUTED SERIALLY AS THE TIME REQUIRED TO EXECUTE
– IT SERIALLY IS LESS THAN 20 SECS AND HENCE PARALLELIZATION
– IS NEEDED

SQL>select count(*) from p5;

 
     select * from v$pq_sesstat
     where statistic = ‘Allocation Height';
STATISTIC                     LAST_QUERY SESSION_TOTAL
—————————————- ————-
Allocation Height                      0            0
SQL>alter session set parallel_min_time_threshold=auto;
– DOP IS COMPUTED COMPUTED FOR THOSE TABLES ALSO FOR WHICH
– DICTIONARY DOP IS DEFAULT. NOTE THAT THE FOLLOWING STATEMENT
– EEXCUTES SERIALLY AS DUE TO SMALL SIZE OF THE TABLE, PARALLELIZATION
– IS NOT REQUIRED
SQL>select  count(*) from pdef;
 
     select * from v$pq_sesstat
     where statistic = ‘Allocation Height’;
STATISTIC                     LAST_QUERY SESSION_TOTAL
—————————————- ————-
Allocation Height                      0            0
– WE CAN OVERRIDE AUTOMATIC COMPUTATION OF DOP BY
GIVING OBJECT LEVEL HINT
SQL>select /*+ parallel (p5 8) */ count(*) from p5;
 
     select * from v$pq_sesstat
     where statistic = ‘Allocation Height';
STATISTIC                     LAST_QUERY SESSION_TOTAL
—————————————- ————-
Allocation Height                      8            0
– IF TABLE SIZE IS SO SMALL THAT PARALLELISM IS NOT REQUIRED
– EVEN SPECIFIED DOP IN THE HINT IS OVERRIDDEN. IF WE EXECUTE
– QUERY ON PDEF WHOSE SIZE IS SMALL, THE QUERY EXECUTES SERIALLY
– EVEN THOUGH WE HAVE SPECIFIED DOP = 8

SQL>select /*+ parallel (pdef 8) */ count(*) from pdef;

 
     select * from v$pq_sesstat
     where statistic = ‘Allocation Height';
STATISTIC                     LAST_QUERY SESSION_TOTAL
—————————————- ————-
Allocation Height                      0            0
References:
———————————————————————————————

Related links :

Home

Database Index
Automatic Degree Of Paralellism – Part-I
Oracle 11g : Automatic DOP – Parallel Threshold
Oracle 11g :  Parallel Statement Queueing
Parallel_Adaptive_Multi_User
Parallel_Min_Percent
Parallel_Min_Servers

                                                                              ——————