set echo     off
set feedback off
set verify   off

set pagesize 999
spool bmark.lis   


Doc

Benchmark Performance Statistics : Release 1

Author: 	Mark Gurry  "Oracle Performance Tuning"

Description:	This script is an early release of a total database tuning 
		script. 


Hints:		It is best to run this script as SYS because some of the 
		tables/views may not be accessible to other users. Most
		of the scripts will run successfully as other DBA users
		if you do not have access to the SYS account. You must
		run the script in SQL*Plus NOT SQL*DBA. 

		You are required to run $ORACLE_HOME/rdbms/admin/catparr.sql
		prior to running this script, to receive buffer cache
		information. You only need to run the catparr script once, 
		and the views that it creates will remain in place. 

Inputs:		The script will request two pieces of information from you.
		Both are being asked to avoid lengthy printouts. Note that
		even if you do no answer Y or y, the output results will be
		in the region of 15 pages.

		(1) Do you request all of the INIT.ORA parameters listed 
		    Print_init_ora_parameters_y_n  To have them listed,
		    enter Y or y.

		(2) Do you wish to look inside the buffer cache to receive
		    a full list of objects. The size of this output and the
		    length of time that it takes to run will be directly 
		    proportional to the DB_BLOCK_BUFFERS setting. The larger
		    the setting, the longer the run will take and the larger
		    the size of the output. 
#


define init.ora = '&&Print_init_ora_parameters_y_n'

Doc

Before we start, it is always nice to work out the versions of the 
the database server. This may prove useful if we need to compare this
output against a future release of Oracle

#


select *                                                
  from v$version                     
/

ttitle ' INIT.ORA Parameters '
column name format a30
column value format a32
		   
select name, value 
  from v$parameter
 where '&&Print_init_ora_parameters_y_n' in ('y','Y');

ttitle ' Undocumented Parameters'

column ksppinm format a34
column ksppivl format a45

select ksppinm, ksppivl from x$ksppi  
where  substr(ksppinm,1,1) = '_'
  and  '&&Print_init_ora_parameters_y_n' in ('y','Y');

column value format 999,999,999,999

doc

    The following listing gives details of the Shared Global Area 
    breakdown in memory. The more significant information is the 
    reading for "free memory", which indicates that the SHARED_POOL_SIZE
    INIT.ORA parameter may be reduced if the free memory is excessive.
    If it is low, you should NOT be decreasing the SHARED_POOL_SIZE.
    Be careful however, because Oracle tends to maintain some free 
    memory even when the buffer cache is flooded with activity and
    should be made larger.

    Other figures that are useful are the  size of the buffer cache
    "db_block_buffers" which is usually required to be at least 20 Meg
    for optimum performance, the sql_area, which is where all of the 
    shared SQL is placed, the dictionary cache, which is where Oracle's
    dictionary is placed, and the "log buffer" which is where all 
    changes are written prior to writing to your redo logs. 

    The log buffer should typically be at least 32078 or larger. 
    The "shared_sql" and "dictionary_cache" sizes are affected by the 
    size that you set the SHARED_POOL_SIZE INIT.ORA parameter to. 
    Unfortunately, the dictionary cache is tuned automatically and not 
    very well by the kernel. Most sites operate most effeciently with
    a shared pool size of at least 30000000.               

#
ttitle ' SGA Statistic Listing '

select  * 
  from v$sgastat;

ttitle ' The Hit Ratio      '

select 
       sum(decode(name, 'consistent gets',value, 0))  "Consis Gets",
        sum(decode(name, 'db block gets',value, 0))  "DB Blk Gets",
        sum(decode(name, 'physical reads',value, 0))  "Phys Reads",
       (sum(decode(name, 'consistent gets',value, 0))  +
        sum(decode(name, 'db block gets',value, 0))  -
        sum(decode(name, 'physical reads',value, 0)))
		       /
       (sum(decode(name, 'consistent gets',value, 0))  +
        sum(decode(name, 'db block gets',value, 0))  )  * 100 "Hit Ratio" 
from v$sysstat st;

doc

     The following output is of Oracle's dictionary cache the is
     stored in memory, in the System Global Area. The dictionary is
     tuned automatically by Oracle. The three columns displayed are the
     dictionary cache parameter, the count that Oracle has set the parameter
     to and the number of times that Oracle has had to reload the dictionary
     information from disk. When Oracle is first started and objects are
     being accessed for the first time, a miss occurs because a disk read 
     has been required to access the information. Ideally from then on,
     there will be no more misses because the dictionary info will be
     nicely placed in memory. Unfortunately this is not the case. Oracle's
     self tuning mechainism does not appear to work very well.

     In the following query, the ideal output would be to see the 
     the getmisses less than or equal to the count, which implies that
     the parameter has been set sufficiently high enough to fit all of
     the dictionary items into memory without having to flush them out
     and then reload them.

     If you are getting deplorable figures, i.e. the getmisses considerably
     higher than the count, increase the SHARED_POOL_SIZE. If this has no
     effect, then join the club requesting that Oracle re-introduce the ability
     for DBAs to tune the dictionary cache.

#

ttitle ' Dictionary Cache (Part of Shared Buffer Pool)'

select parameter, count, getmisses 
  from v$rowcache
where getmisses > count;

doc
 
   The following script shows the spread of disk I/O's. Ideally the 
   disk I/O's should be even across disks. If the disk I/O on one
   of the disks is too high, consider moving one of the datafiles on
   that disk to another disk. If you are using raw devices, it is
   handy to make the raw devices consistent sizes for this purpose.
   If you can't move one of the datafiles, consider moving objects
   into tablespaces on different disks.

   If there is a large amount of activity on one of the disks, there
   is often an untuned query causing the damage. Be aware of which
   table is causing the problems. The V$SQLAREA table has a column
   called disk reads as well as the SQL statement that was performed.
   If you select all statements with > 1000 disk reads, and order it
   by disk reads desc, it is often an indicator of the problem query.

   See the selection from the table later in this script.

   One trick that is often done is to alternate rollbacks one disk to
   the next into alternate tablespaces, for example, rollback1 will be
   created on the rollback_disk1 tablespace, rollback2 on  rollback_disk2
   tablespace, rollback3 on  rollback_disk1 tablespace, rollback4 on
   rollback_disk2 tablespace and so on. Of course, you must have the
   rollback_disk1 tablespace on a separate disk drive to rollback_disk2.


#

drop table tot_read_writes;

create table tot_read_writes
 as select sum(phyrds) phys_reads, sum(phywrts) phys_wrts
      from v$filestat;

ttitle ' Disk I/O s by Datafile '

column name format a30;
column phyrds format 999,999,999;
column phywrts format 999,999,999;
column read_pct format 999.99;         
column write_pct format 999.99;        
 
select name, phyrds, phyrds * 100 / trw.phys_reads read_pct, 
       phywrts,  phywrts * 100 / trw.phys_wrts write_pct
from  tot_read_writes trw, v$datafile df, v$filestat fs
where df.file# = fs.file# 
order by phyrds desc;

doc

The next group of figures are the those for table accesses.

The "table fetch row continued rows" have been accessed and the row has 
either been chained or migrated. Both situations result from part of
a row has been forced into another block. The distinction is for
chained rows, a block is physically to large to fit in one physical 
block. 

In these cases, you should look at increasing the db_block_size
next time you re-build your database, and for other environments, e.g.
when you move your application into production. Migrated rows are rows
that have been expanded, and can no longer fit into the same block. In these
cases, the index entries will point to the original block address, but the
row will be moved to a new block. The end result is that FULL TABLE SCANS
will run no slower, because the blocks are read in sequence regardless of
where the rows are. Index selection of the row will cause some degradation
to response times, because it continually has to read an additional block.
To repair the migration problem, you need to increase the PCTFREE on the
offending table.

The  other values include "table scans (long tables)" which is a scan 
of a table that has > 5 database blocks and table scans (short tables)
which is a count of Full Table Scans with 5 or less blocks. These values
are for Full Table Scans only. Any Full Table Scan of a long table can
be potentially crippling to your application's performance. If the number
of long table scans is significant, there is a strong possibility that
SQL statements in your application  need tuning or indexes need to be added.

To get an appreciation of how many rows and blocks are being accessed on
average for the long full table scans:                           

Average Long Table Scan Blocks = 
             table scan blocks gotten - (short table scans * 5) 
                                  / long table scans


Average Long Table Scan Rows = 
             table scan rows gotten - (short table scans * 5) 
                                  / long table scans

The output also includes values for "table scan (direct read)" which
are those reads that have bypassed the buffer cache, table scans 
(rowid ranges) and table scans (cache partitions).


#

select name, value 
  from v$sysstat
where  name like '%table %'
/

drop view Full_Table_Scans;

create view Full_Table_Scans as
 select 
       ss.username||'('||se.sid||') ' "User Process", 
       sum(decode(name,'table scans (short tables)',value)) "Short Scans",
       sum(decode(name,'table scans (long tables)', value)) "Long Scans",
          sum(decode(name,'table scan rows gotten',value)) "Rows Retreived"
  from v$session ss, v$sesstat se, v$statname sn
 where  se.statistic# = sn.statistic#
   and (     name  like '%table scans (short tables)%'
          OR name  like '%table scans (long tables)%'
          OR name  like '%table scan rows gotten%'     )
   and  se.sid = ss.sid
   and   ss.username is not null
group by ss.username||'('||se.sid||') '
/
column  "User Process"     format a20;  
column  "Long Scans"       format 999,999,999;   
column  "Short Scans"      format 999,999,999;   
column  "Rows Retreived"   format 999,999,999;   
column  "Average Long Scan Length" format 999,999,999;   

ttitle ' Table Access Activity By User '

select "User Process", "Long Scans", "Short Scans", "Rows Retreived"
  from Full_Table_Scans 
 order by "Long Scans" desc; 

ttitle 'Average Scan Length of Full Table Scans by User '

select "User Process", ( "Rows Retreived" - ("Short Scans" * 5))
                            / ( "Long Scans" ) "Average Long Scan Length"
  from Full_Table_Scans 
 where "Long Scans" != 0
 order by "Long Scans" desc; 


doc

   The ideal is to have as much sorting in memory as possible. The
   amount of memory available for sorting in memory is set by assigning 
   a value to the INIT.ORA parameter SORT_AREA_SIZE. If there is a large 
   amount of sorting being done on disk, consider enlarging the 
   SORT_AREA_SIZE. Be aware that this parameter applies to all users
   and there must be sufficient memory to adjust the parameter upwards.
   
   Some sorts to disk are simply too large to be done in memory. In these
   cases, make sure that the DEFAULT INITIAL and NEXT extents are sized
   sufficiently large enough to avoid unnecessary dynamic extension.
    
#

ttitle ' Checking any Disk Sorts'

select name, value from v$sysstat
where  name like 'sort%';

doc

     The following query indicates the amount of rollbacks performed
     on the transaction tables. 

(i)  'transaction tables consistent reads - undo records applied'
       is the total # of undo records applied to rollback transaction
       tables only

      It should be < 10% of the total number of consistent changes

(ii) 'transaction tables consistent read rollbacks'
       is the number of times the transaction tables were rolled back

     It should be less than 0.1 % of the value of consistent gets


     If either of these scenarios occur, consider creating more rollback
     segments, or a greater number of extents in each rolback segment. A
     rollback segment equates to a transaction table and an extent is like
     a transaction slot in the table. 

     Another interesting approach that Oracle mentions is to do a 
     SELECT COUNT(*) FROM TNAME(s); where the TNAMES are the tables
     that the blocks that have been modified and committed occur in.
     This marks the transactions as committed and later queries do not
     have to find the status of the transactions in the transaction tables
     to determine the status of the transactions.

#
   
ttitle 'Amount of Rollbacks on Transaction Tables'

select name, value      
  from v$sysstat
 where name in
   ('consistent gets',
    'consistent changes',
    'transaction tables consistent reads - undo records applied',
    'transaction tables consistent read rollbacks');

select 'Tran Table Consistent Read Rollbacks > 1% of Consistent Gets' aa,
       'Action: Create more Rollback Segments'
  from v$sysstat
 where decode (name,'transaction tables consistent read rollbacks',value)
                   * 100
                    /
       decode (name,'consistent gets',value) > 0.1
   and name in ('transaction tables consistent read rollbacks',
                     'consistent gets')
   and value > 0
;   
 
select 'Undo Records Applied > 10% of Consistent Changes' aa,
       'Action: Create more Rollback Segments'
  from v$sysstat
 where decode 
    (name,'transaction tables consistent reads - undo records applied',value)
                   * 100
                    /
       decode (name,'consistent changes',value) > 10  
   and name in 
         ('transaction tables consistent reads - undo records applied',
                     'consistent changes')
   and value > 0
;
  
doc

   The last two values returned in the following query are the number
   of times data blocks have been rolled back. If the number of rollbacks
   is continually growing, it can be due to inappropriate work practice.
   Try breaking long running queries into shorter queries, or find another
   method of querying hot blocks in tables that are continually changing. 

#

select name , value
  from v$sysstat
 where name in
    ('data blocks consistent reads - undo records applied',
     'no work - consistent read gets',   
     'cleanouts only - consistent read gets',
     'rollbacks only - consistent read gets',
     'cleanouts and rollbacks - consistent read gets')
;

doc  

    Each rollback segment has a transaction table that controls the 
    transactions accessing the rollback segment. Oracle documentation
    says that the transaction table has approximately 30 slots in the 
    table if your database has a 2k block size. The following query
    lists the number of waits on a slot in the transaction tables. The
    ideal is to have the waits zero, but in the real world this is not 
    always achievable. The should be as close to zero as possible.
    At the very worst, the ratio of gets to waits should be around 99%.

#

select usn "Rollback Table", Gets, Waits , xacts "Active Transactions"
  from v$rollstat;

doc

    It is important that rollback segments do not extend very often. 
    Dynamic extension is particularly  damaging to performance, because
    each extent used (or freed) is written to disk immediately. See the 
    uet$ and fet$ system tables. 

    Some sites have a large amount of performance degradation when
    the optimal value is set, because the rollback is continually 
    shrinking and extending. This situation has been improved to 
    make the SMON less active where the PCTINCREASE is set to 0. 

    Note: in this case, free extents will not be coalesced. When 
    a rollback throws extents after it has shrunk back, it will hopefully
    find an extent of the correct size. If you would like the coalescing
    to continue, set the PCTINCREASE on the tablespace to 1. 

    We usually recommend NOT to use the OPTIMAL setting. 

    The listing below provides figures on how many times each rollback
    segment has had to extend and shrink.

#

select usn, extends, shrinks, hwmsize, aveshrink
  from v$rollstat;


doc

    The following query shows you the amount of overall changes that are 
    made to data blocks in the database 'db block changes'. The second
    value returned is the number of blocks that have been rolled back
    'rollback changes - undo records applied' and the third value is the
    number of rollback transactions 'transaction rollbacks'.

    When a rollback takes place, it can hold many resources until the 
    rollback is complete. If the rollbacks are significant in size, i.e.
    the cause of the rollbacks should be investigated. A way to check
    this is to check 'rollback changes - undo records applied' /
                          'transaction rollbacks'.

    If the overall percentage of rows rolled back as compared to those 
    changes is significant (> .5%), you should question why are so many
    rows being rolled back. The causes could be many, including a network
    failure where sessions are continually being lost. Consider introducing
    SAVEPOINTS where appropriate or using shorter transactions to have 
    fewer rows to rollback (i.e. more frequent commits and less rows being
    processed).

#
doc

ttitle 'Get all V$SYSSTAT'

column value format 999999999;

select * from v$sysstat;
#

doc

   The following query displays information on wait contention for 
   an Oracle object. The two figures of particular importance are
   the 'data block' and the 'undo header' waits. The undo header 
   indicates a wait for rollback segment headers which can be solved by 
   adding rollback segments. 

   The data block wait is a little harder to find the cause of and 
   a little harder to fix. Basically, transactions are contending for
   hot data blocks, because it is being held by another transaction, 
   or contending for shared resources within the block. e.g. transaction
   entries (set by the INITRANS parameter) and rows. The INITRANS  
   storage parameter sets the number of transaction slots set aside 
   within each table/index. The default INITRANS is 1. If more than
   one transaction is waiting to access a block, a second transaction
   slot will have to be created. Each transaction slot uses 23 bytes.

   The ideal count on the data block waits is 0.   This is usually not
   achievable in the real world, because the storage overhead of increasing
   the INITRANS is usually not justified given the large amount of
   storage overhead that it will introduce. Data block contention can cause
   problems and enlarging INITRANS can improve performance, so don't
   dismiss the idea of enlarging INITRANS immediately.   Potential 
   performance improvements CAN be significant.

   Identifying the blocks that are experiencing contention is quite
   difficult to catch. Your best chance is to examine the output from the
   query from the V$SESSION_WAIT table below. You may consider increasing
   the PCTFREE in the table to have fewer rows per block, or make a design
   change to your application to have fewer transactions accessing the same
   block. 

#

ttitle 'Get All Waits' 

column count format 9999999;
select class, count 
  from v$waitstat;


ttitle 'Sessions experiencing block level contention'



select ss.username, sw.p1 "File", sw.p2  "Block"
  from v$session_wait sw, v$session ss
 where event = 'buffer busy waits'
   and ss.sid = sw.sid;

ttitle ' The Object that has Experienced Contention '
doc

column "Owner" format a16
column "Segment Name" format a30

select owner "Owner" , segment_name "Segment Name"
from  dba_extents
where (file_id, block_id)
in
(select file_id, block_id
   from dba_extents, v$session_wait vw 
  where file_id = vw.p1    
  and   (p1   , p2    ) 
       in
        (select p1   , p2      
           from v$session_wait
          where event = 'buffer busy waits')
  and   block_id= (select max(block_id) 
                    from dba_extents
                   where block_id < vw.p2      ))
/
#

doc

   The following query indicates a transaction waiting for resources
   within a block currently held by another transaction. This can 
   be caused by having to throw another transaction slot in the
   block. 

   In extreme cases, throwing many transaction slots into a 
   block can cause the migration of a row, which will have a similar 
   effect to chaining, when the block is accessed via an index, i.e.
   two blocks may have to be read to retrieve a row. Remember that each
   transaction slot uses 23 bytes. The smaller the DB_BLOCK_SIZE the
   greater the possibility of row migration. The other cause of the   
   locks in the following query is that a row within the block are 
   sought  after by two separate transactions. This problem is usually
   only fixable through an application change, where you postpone the
   changes to the rows until the last possible moment in the transaction. 

#


ttitle 'Transactions Experiencing Lock Contention'

select * 
  from v$lock 
 where type = 'TX';


Doc

The following query scans through the buffer cache and counts the number
of buffers in the various states. The three main states are CUR which is
blocks read but not dirtied, CR which are blocks that have been dirtied 
and are remaining in cache with the intention of supplying the new values 
to queries about to start up. FREE indicates buffers that are usable to 
place new data being read into the buffer cache. You occasionally get 
buffers in a status of READ which are those buffers currently being read 
into the buffer cache. 

The major information from the query is if the FREE count is high, say
> 50% of overall buffers, you may consider decreasing the DB_BLOCK_BUFFERS
parameter. Note however, that Oracle attempts to maintain a free count > 
0, so consistently having free buffers does not automatically imply that
you should have lower the parameter DB_BLOCK_BUFFERS.
ttitle ' Free Buffer in Buffer Cache'

To create the view v$bh, which is used by this script, you must run    
the script $ORACLE_HOME/rdbms/admin/catparr.sql

#

ttitle ' Current Buffer Cache Usage     '

select status, count(*) 
  from v$bh 
group by status;

drop table bh_temp;

create table bh_temp 
    as select * from v$bh;

doc

This is required for pre-7.1 of the database. Use the script below for 
all release 7.1 and after. Note: You MUST run the script
$ORACLE_HOME/rdbms/admin/catparr.sql prior to running this script.

ttitle ' Detailed Buffer Cache Usage by Objects '

column owner format a12;
column segment_type format a10;
column segment_name format a22;
column tablespace_name format a16;
column "Num Buffers" format 9999;

select e.owner, e.segment_type, e.segment_name, tablespace_name, count(*)
				   "Num Buffers"
from dba_extents e , bh_temp b
where file# / 4 = e.file_id
and b.block# between e.block_id and e.block_id + e.blocks
group by e.owner, e.segment_type, e.segment_name, tablespace_name;

Remember that the buffer cache stores Tables, Indexes, Dictionary Tables,
Sort Temporary Tables and Rollback Segments. Oracle 7.1, 7.2 and 7.3    
of Oracle introduce options to bypass the buffer cache for some of these 
operations.            

See the 7.2 parameter SORT_DIRECT_TRUE which bypasses the buffer cache and
allows sorts to run as much as 3 times as fast.

In 7.1.6 parallel table scans can bypass the buffer cache if you set 
compatible=7.1.6

In 7.2 Oracle turns off logging (not strictly buffer cache) if you use the
UNRECOVERABLE option. When building an index, you can bypass writing to
redo logs by setting this parameter. The direct load path is used also to 
bypass the buffer cache. One interesting point is that INRECOVERABLE is
the default when Archiving is not enabled.	

#

set pagesize 66  


ttitle ' Breakdown of what is Currently Being Stored in the Buffer Cache'

select kind, name, status, count(*) 
  from v$cache
 where substr('&Look_inside_Buffer_Cache?',1,1) in ('y','Y')
 group by kind, name, status;

doc

When you enlarge the DB_BLOCK_BUFFERS, if you see decreases in the 
values returned in the following query, it is almost always associated
with a performance improvement. Keep in mind that there are two aspects to 
buffer cache tuning (i) having data in memory is up to several thousand
times faster than finding it on disk and (ii) when Oracle is reading data
into memory and is forced to clear buffers quickly because buffer cache
is too small, it often does so in "panic mode" which can have a harmful
effect on performance.

This means that the HIT RATIO alone tells only part of the story.

The following figures tell us how much panicking the DBWR has had to do.
It is best if the cache is sufficiently large enough to clean blocks out
using the normal mechanisms rather than in panic mode.

NOTE: The DBWR process writes to disk under the following circumstances

(i) When a process dirties a buffer and the number of dirty buffers in the
    buffer cache reaches the count set by the parameter DB_BLOCK_WRITE_BATCH.
    If this is the case 50% of the dirty buffers are written to disk.

(ii) When a process searches DB_BLOCK_MAX_SCAN_CNT buffers in the list      
     without finding a free buffer. If this is the case it signals the 
     DBWR to write to make way for free buffers.

(iii) Every 3 seconds (when a timeout occurs).

(iv) When a checkpoint occurs the LGWR instructs the DBWR to write.

The last situation is interesting because it implies that badly tuned 
checkpoints will potentially cause DBWR problems.

The rows returned in the query below indicate the following:

DBWR Checkpoints: is the number of times that checkpoints were sent to the
database writer process DBWR. The log writer process hands a list of modified 
blocks that are to be written to disk. The "dirty" buffers to be written 
are pinned and the DBWR commences writing the data out to the database.
It is usually best to keep the DBWR checkpoints to a minimum, although 
if there are too many dirty blocks to write out to disk at the one time
due to a "lazy" DBWR, there may be a harmful effect on response times for
the duration of the write.  See the parameters LOG_CHECKPOINT_INTERVAL and
LOG_CHECKPOINT_TIMEOUT which have a direct effect on the regularity of 
checkpoints. The size of your red logs  can also have an effect on the
number of checkpoints if the LOG_CHECKPOINT_INTERVAL is set to a size 
larger than your redo logs and the LOG_CHECKPOINT_TIMEOUT is longer than
the time it takes fill a redo log or it has not been set. 

DBWR timeouts: the # times that the DBWR looked for dirty blocks to
write to the database. Timeouts occur every 3 seconds if the DBWR is idle.           
 
DBWR make free requests: is the number of messages recieved requesting the
database writer process to make the buffers free. This value is a key 
indicator as to how effectively your DB_BLOCK_BUFFERS parameter is tuned.
If you increase DB_BLOCK_BUFFERS and this value decreases markedly, there
is a very high likelihood that the DB_BLOCK_BUFFERS was set too low.

DBWR free buffers found: is the number of buffers that the DBWR found 
on the lru chain that were already clean. You can divide this value by 
the DBWR make free requests to obtain the number of buffers that were 
found which were free and clean (i.e. did NOT have to be written to disk).

DBWR lru scans: the number of times that the database writer scans the lru
for more buffers to write. The scan can be invoked either by a make free
request or by a checkpoint. 

DBWR summed scan depth: can be divided by DBWR lru scans to determine the
length of the scans through the buffer cache. This is NOT the number of   
buffers scanned. if the write batch is filled and a write takes place
to disk, the scan depth halts. 

DBWR buffers scanned: is the total  number of buffers scanned when looking 
for dirty buffers to write to disk and create free space. The count 
includes both dirty and clean buffers.  It does NOT halt like the 
DBWR summed scan depth.

#


ttitle 'The Amount of Times Buffers Have Had to Be Cleaned Out'

select name , value 
  from v$sysstat
 where name like 'DBW%';


ttitle 'The Average Length of the Write Request Queue'

column  "Write Request Length" format 999,999.99

select  decode (name, 'summed dirty queue length', value)
               /
        decode (name, 'write requests', value) "Write Request Length"
  from v$sysstat
 where  name in ( 'summed dirty queue length'
                 ,'write requests')
   and  value > 0
/

doc 

The information listed in the next query is the count for "Dirty Buffers
Inspected". This figure indicates that the DBWR process can't keep up 
through natural atrition and a dirty buffer has been aged out through
the LRU queue, when a user process has been looking for a free buffer
to use. This value should ideally be zero. The value is probably THE key
indicator of the DB_BLOCK_BUFFERS init.ora parameter being set too small,
particularly if you enlarge the DB_BLOCK_BUFFERS parameter and the value
reduces after each decrease. 

Also included in the output, is the value for "Free Buffers Inspected".
This figure indicates the number of times a user process has scanned the 
LRU list for free buffers. A high value would indicate that there are too 
many dirty blocks on the LRU list and the user process had to stop because
the dirty queue was at its threshold. 

#
column "Dirty Buffers" format 999,999,999;
column "Free Buffers Inspected" format 999,999,999;

ttitle 'Lazy DBWR Indicators - Buffers Inspected'

select  decode (name, 'dirty buffers inspected', value) "Dirty Buffers",
        decode (name, 'free buffer inspected', value) "Free Buffers Inspected"
  from v$sysstat
 where  name in ( 'dirty buffers inspected'
                 ,'free buffer inspected')
   and  value > 0
/

doc

The following query breaks down hit ratios by user. The lower the hit ratio
the more disk reads that have to be performed to find the data that the user 
is requesting. If a user is getting a low hit ratio (say < 60%), it is often
caused because  the user not using indexes effectively or an absence of 
indexes. It can sometimes be quite OK to get a low hit ratio if the user
is accessing data that has not been accessed before and cannot be shared
amongst users. Note: OLTP applications ideally have a hit ratio in the mid
to high 90s. 

The second query lists the tables that the user processes with a hit ratio
less than 60% were accessing. Check the tables to ensure that there are no
missing indexes.

#

ttitle ' User Hit Ratios'

column "Hit Ratio" format 999.99
column  "User Session" format a15;

select se.username||'('|| se.sid||')' "User Session",
       sum(decode(name, 'consistent gets',value, 0))  "Consis Gets",
        sum(decode(name, 'db block gets',value, 0))  "DB Blk Gets",
        sum(decode(name, 'physical reads',value, 0))  "Phys Reads",
       (sum(decode(name, 'consistent gets',value, 0))  +
        sum(decode(name, 'db block gets',value, 0))  -
        sum(decode(name, 'physical reads',value, 0)))
		       /
       (sum(decode(name, 'consistent gets',value, 0))  +
        sum(decode(name, 'db block gets',value, 0))  )  * 100 "Hit Ratio" 
  from  v$sesstat ss, v$statname sn, v$session se
where   ss.sid    = se.sid
  and   sn.statistic# = ss.statistic#
  and   value != 0
  and   sn.name in ('db block gets', 'consistent gets', 'physical reads')
group by se.username, se.sid;


drop table user_hit_ratios;


create table user_hit_ratios as
(select se.username||'('|| se.sid||')' "User Session",
       sum(decode(name, 'consistent gets',value, 0))  "Consistent Gets",
        sum(decode(name, 'db block gets',value, 0))  "DB Block Gets",
        sum(decode(name, 'physical reads',value, 0))  "Physical Reads",
       (sum(decode(name, 'consistent gets',value, 0))  +
        sum(decode(name, 'db block gets',value, 0))  -
        sum(decode(name, 'physical reads',value, 0)))
		       /
       (sum(decode(name, 'consistent gets',value, 0))  +
        sum(decode(name, 'db block gets',value, 0))  )  * 100 "Hit Ratio" 
  from  v$sesstat ss, v$statname sn, v$session se
where   ss.sid    = se.sid
  and   sn.statistic# = ss.statistic#
  and   value != 0
  and   sn.name in ('db block gets', 'consistent gets', 'physical reads') 
group by se.username, se.sid);

break on "User Session"
doc

ttitle 'Objects Being Used by Users with Hit Ratio < 60'

select se0.username||'('|| se0.sid||')' "User Session",
       substr(owner, 1,12) "Object Owner",                              
       substr(object,1,30) "Object"
  from  v$access ac, v$session se0
where   ac.sid    = se0.sid
  and   ob_typ   = 2
  and   60 > 
  (select "Hit Ratio" from user_hit_ratios
   where se0.username||'('|| se0.sid||')' = "User Session")
order by username, se0.sid, owner ;


column "User Session"  format a12;

ttitle 'Cursors that Users currently have Open Where User Hit Ratio < 60%'

select distinct username||'('||v$session.sid||')' "User Session",  sql_text 
from v$open_cursor , v$session
where v$session.saddr       = v$open_cursor.saddr   
  and   60 > 
  (select "Hit Ratio" from user_hit_ratios
   where username||'('||user_hit_ratios.sid||')' = "User Session")
order by username, v$session.sid;
/


ttitle 'Cursors Currently Running for Users with Hit Ratio < 60% '

select username||'('||v$session.sid||')' "User Session",  sql_text 
from v$open_cursor , v$session
where v$session.sql_address = v$open_cursor.address
  and v$session.sql_hash_value = v$open_cursor.hash_value
  and   60 > 
  (select "Hit Ratio" from user_hit_ratios
   where username||'('||user_hit_ratios.sid||')' = "User Session")
order by username, v$session.sid;
/
#
column "Response" format 999,999,999.99;
column nl newline;

ttitle 'List Statements in Shared Pool with the Most Disk Reads'

select sql_text nl, 'Executions='|| executions  nl,
      'Expected Response Time in Seconds= ', 
     disk_reads / decode(executions, 0, 1, executions) / 50 "Response"  
  from v$sqlarea
where  disk_reads / decode(executions,0,1, executions) / 50 > 10 
order  by executions desc;

column "Response" format 999,999,999.99

ttitle 'List Statements in Shared Pool with the Buffer Scans'   

select sql_text nl, 'Executions='|| executions  nl,
            'Expected Response Time in Seconds= ', 
            buffer_gets / decode(executions, 0, 1, executions) / 500 "Response"  
  from v$sqlarea
where  buffer_gets / decode(executions, 0,1, executions) / 500 > 10 
order  by executions desc;

ttitle 'List Statements in Shared Pool with the Most Loads'

select sql_text, loads 
  from v$sqlarea a
where  loads > 100 
order  by loads desc;

doc
column username format a12; 
column ses.sid format 999999;

ttitle 'User Resource Usage'
select ses.sid, ses.username, sn.name, sest.value
  from v$session ses, v$statname sn, v$sesstat sest
 where ses.sid=sest.sid
   and sn.statistic# = sest.statistic#
   and sest.value is not null
   and sest.value != 0            
 order by ses.username, ses.sid, sn.name;

column "User Session"  format a12;
ttitle 'Cursors that Users currently have Open'
select username||'('||v$session.sid||')' "User Session",  sql_text 
from v$open_cursor , v$session
where v$session.saddr       = v$open_cursor.saddr    
/

ttitle 'Cursors Currently Running for a User '
select username||'('||v$session.sid||')' "User Session",  sql_text 
from v$open_cursor , v$session
where v$session.sql_address = v$open_cursor.address
  and v$session.sql_hash_value = v$open_cursor.hash_value
/

#

doc
    
   The following figures are the reloads required for SQL, PL/SQL,
   packages and procedures. The ideal is to have zero reloads because 
   a reload by definitions is where the object could not be maintained 
   in memory and Oracle was forced to throw it out of memory, and then
   a request has been made for it to be brought back in. If your reloads
   are very high, try enlarging the SHARED_POOL_SIZE parameter and
   re-check the figures. If the figures continue to come down, continue 
   the SHARED_POOL_SIZE in increments of 5 Meg.

#

ttitle ' Total Shared Pool Reload Stats '

select namespace, reloads 
  from v$librarycache;

doc 

The following three queries obtain information on the SHARED_POOL_SIZE.

The first query lists the packages, procedures and functions in the 
order of largest first.

The second query lists the number of reloads. Reloads can be very 
damaging because memory has to be shuffled within the shared pool area
to make way for a reload of the object.

The third parameter lists how many times each object has been executed.

Oracle has provided a procedure which is stored in $ORACLE_HOME/rdbms/admin
called dbmspool.sql The SQL program produces 3 procedures. A procedure
called keep (i.e. dbms_shared_pool.keep) can be run to pin a procedure in
memory to ensure that it will not have to be re-loaded.    

Oracle 7.1.6 offers 2 new parameters that allow space to be reserved for
procedures/packages above a selected size. This gives greater control 
over the avoidance of fragmentation in the SHARED POOL.

See the parameters SHARED_POOL_RESERVED_SIZE and
                            SHARED_POOL_RESERVED_MIN_ALLOC.

They are listed later in this report. 

#

set pagesize 999
column owner format a16
column name  format a36
column sharable_mem format 999,999,999
column executions   format 999,999,999

ttitle ' Memory Usage of Shared Pool Order - Biggest First'

select  owner, name||' - '||type name, sharable_mem from v$db_object_cache
where sharable_mem > 10000
  and type in ('PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'PROCEDURE')
order by sharable_mem desc
/

ttitle ' Loads into Shared Pool  - Most Loads First'

select  owner, name||' - '||type name, loads , sharable_mem from v$db_object_cache
where loads > 3 
  and type in ('PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'PROCEDURE')
order by loads desc
/

ttitle ' Executions of Objects in the  Shared Pool  - Most Executions First'

select  owner, name||' - '||type name, executions from v$db_object_cache
where executions  > 100
  and type in ('PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'PROCEDURE')
order by executions  desc
/

doc

The next query lists the number of open cursors that each user is 
currently utilising. Each SQL statement that is executed is stored
partly in the Shared SQL Area and partly in the Private SQL Area.
The private area is further broken into 2 parts, the persistent area
and the runtime area. The persistent area is used for binding info. 
The larger the number of columns in a query, the larger the persistent
area. The size of the runtime area depends on the complexity of the
statement. The type of statement is also a factor. An insert, update
or delete statement will use more runtime area than a select. 

For insert, update and delete statements, the runtime area is freed
immediately after the statement has been executed. For a query, the 
runtime area is cleared only after all rows have been fetched or the
query is cancelled. 

What has all this got to do with open cursors?

A private SQL area continues to exist until the corresponding cursor 
is closed. Note: the runtime area is freed but the persistent (binding)
area remains open. If the statement is re-used, leaving cursors open is
not bad practice, if you have sufficient memory on your machine. Leaving
cursors that are not likely to be used again is bad practice, once 
again, particularly if you are short of memory. The number of private 
areas is limited by the setting of OPEN_CURSORS init.ora parameter. The
user process will continue to operate, despite having reached the OPEN_
CURSOR limit. Cursors will be flushed and will need to be pe-parsed the
next time they are accessed.

Recursive calls are used to handle the re-loading of the cursors if
the have to be re-binded after being closed.

The data in the following query lists each user process, the number of
recursive calls (the lower the better), the total opened cursors
cumulative and the current opened cursors. If the number of current opened
cursors is high (> 50), question why curors are not being closed. If the
number of cumulative opened cursors and recursive calls is significantly 
larger for some of the users, determine what transaction they are running
and determine if they can leave cursors open to avoid having to re-bind
the statements and avoid the associated CPU requirements.


#


drop view user_cursors;

create view user_cursors as
 select 
         ss.username||'('||se.sid||') ' user_process, 
          sum(decode(name,'recursive calls',value)) "Recursive Calls",
          sum(decode(name,'opened cursors cumulative',value)) "Opened Cursors",
          sum(decode(name,'opened cursors current',value)) "Current Cursors"
  from v$session ss, v$sesstat se, v$statname sn
 where  se.statistic# = sn.statistic#
   and (     name  like '%opened cursors current%'
          OR name  like '%recursive calls%'
          OR name  like '%opened cursors cumulative%')
   and  se.sid = ss.sid
   and   ss.username is not null
group by ss.username||'('||se.sid||') '
/

set pagesize 999

ttitle 'Per Session Current Cursor Usage '

column USER_PROCESS format a25;
column "Recursive Calls" format 999,999,999;
column "Opened Cursors"  format 99,999;
column "Current Cursors"  format 99,999;

select * from user_cursors   
 order by "Recursive Calls" desc
/

doc

The next output lists the CPU usage on a per-user basis. If a user process
has considerably more CPU usage, it is worth investigating. The causes can
be many, ranging from untuned SQL statements, indexes missing, cursors
being closed too often, or an ad hoc user running rampant. The high CPU 
usage may also be acceptable depending on the type of tasks that the person
is performing.

#

ttitle ' CPU Used By Session Information '

select substr(name,1,30) parameter,
         ss.username||'('||se.sid||') ' user_process, value
  from v$session ss, v$sesstat se, v$statname sn
 where  se.statistic# = sn.statistic#
   and  name  like '%CPU used by this session%'
   and  se.sid = ss.sid
 order  by substr(name,1,25), value desc
/
ttitle ' Internal Oracle Locking Problems '

set heading off

select 'Warning: Enqueue Timeouts are '||value||'. They should be zero' line1,
       'Increase the INIT.ora parameter ENQUEUE_RESOURCES'
  from v$sysstat
 where name  = 'enqueue timeouts'
   and value > 0
/

doc

The following report lists tables without any indexes on then whatsoever.
It is unusual for tables not to need an index. Even small tables require
an index to guarantee uniqueness. Small tables also  require indexes for
joining, because a full table scan will always drive a query (unless a 
hint is used). If you are scanning through many rows in a large table
using an index range scan and are looking up a reference table to expand 
a code into the description stored in the smaller table, the query will 
take considerably longer because the small table will be the driving 
table. Larger tables can drag a machine to its knees if they are 
continually accessed without going through an index. 



#
ttitle 'Report on all Tables Without Indexes'

select owner, table_name from all_tables
MINUS
select owner, table_name from all_indexes;

doc     

The next enquiry gives you a list of indexes that have the same column 
as the leading column in the index. These indexes can cause problems if
queries use the incorrect index. e.g. Oracle will utilise the index that
was created most recently if two indexes are of equal ranking. This can 
cause different indexes to be utilised from one environment to the next
e.g. from DEV to QA to PROD. Each environment may have had its indexes 
created in a different sequence. I strongly recommend that you use hints
in you programs to overcome these problems and force specific queries to 
use a specified index.

The following information does not automatically indicate that an index
is incorrect, but each index should be viewed to justify its existence.

#

column table_owner format a20;
column table_name  format a26;
column column_name format a26;

ttitle 'Indexes which may be Superfluous'

select table_owner, table_name ,column_name 
  from  all_ind_columns 
 where  column_position =1
 group  by table_owner, table_name , column_name
having  count(*) > 1 
/

Doc

The following output lists all foreign keys that do not have an index
on the Child table, for example, we have a foreign key on the EMP table
to make sure that it has a valid DEPT row existing. The foreign key is 
placed on the EMP (deptno) column pointing to the DEPT (deptno) primary
key.

Obviously the parent table DEPT requires an index on deptno for the
foreign key to point to. The effect of the foreign key that is not
widely known is that unless an index is placed on the child table on
the columns that are used in the foreign key, a share lock occurs on the 
parent table for the duration of the insert, update or delete on the 
child table. 

What is a share lock, you ask? The effect of a share lock is that all
query users hoping to access the table have to wait until a single update
user on the table completes his/her update. Update users cannot perform 
their update until all query users complete their queries against the table.
The bottom line is that if the parent table is a volatile table, the 
share lock can cause the most incredible performance degradation. At a 
recent benchmark, we had the entire benchmark grind to a halt because of this
locking situation. If the parent table is a non-volatile table, you may
be able to get away without the index on the child table, because the lock
on the parent table is of no importance.

The negative factor of the index on the child table is that I have observed
tables with as many as 30 indexes on them and the performance degradation 
has been caused by maintaining the excessive number of indexes. My advice 
to these sites has been to only use foriegn key constraints on columns
that have an index which can be used for other purposes (e.g. reporting)
or that point to a non-volatile reference table.  Most tables have difficulty
maintaining acceptable performance if they have > 10 indexes on them.

You may wish to take the foreign keys offline during the day and put them 
online at night to report any errors into an exceptions table. You should 
do this when the parent table is not being accessed.

#

ttitle ' Foreign Constraints and Columns Without an Index on Child Table'

select acc.owner||'-> '||acc.constraint_name||'('||acc.column_name
               ||'['||acc.position||'])'||' ***** Missing Index'
from   all_cons_columns acc, all_constraints ac
where  ac.constraint_name = acc.constraint_name
 and   ac.constraint_type = 'R'
 and   (acc.owner, acc.table_name, acc.column_name, acc.position) in
 (select acc.owner, acc.table_name, acc.column_name, acc.position 
    from   all_cons_columns acc, all_constraints ac
   where  ac.constraint_name = acc.constraint_name
     and   ac.constraint_type = 'R'
  MINUS
  select table_owner, table_name, column_name, column_position
    from all_ind_columns)
order by acc.owner, acc.constraint_name, acc.column_name, acc.position;
   
doc

Having multiple extents on objects causes performance degradation when 
performing full scans. The throwing of the extents requires recursive 
calls to the uet$ and fet$ tables (used extents and free extents 
dictionary tables) which is also particularly damaging to performance
if done too often. Most of the database objects below can potentially
require re-building, but keep the following exceptions in mind...

I have observed performance degradation as much as 10 times longer
runtime for multiple extents compared to a single extent.

(1) Parallel data loading and other parallel processing depends on 
objects having multiple extents for them to operate correctly. 

(2) Rollback segments require multiple extents to service multiple users
sharing  the rollback segment.

(3) Multiple extents on a very large table will have a much less          
harmful effect than multiple extents on a smaller table. Of course, the
re-building of the table (and its indexes) may take an excessive amount
of time. 

If you do need to do a re-org, consider the UNRECOVERABLE option
to bypass the buffer cache and parallel index loading, to speed up the
re-build.

One other thing you may consider is to have a lower PCTFREE when
rebuilding the table and then altering the table to enlarge the PCTFREE
to a larger value after the tables data has been loaded. Make sure that
you re-load you data without indexes, constraints and triggers. Add the 
indexes and constraints after the data has been loaded. Add the triggers
also, but keep in mind that you may have to perform the tasks that the 
trigger would have performed, because the trigger only becomes effective
on data created after the trigger has been created.

#

column segment_name    format a26;
column owner           format a12;
column tablespace_name format a12;
column seg_count       format a14;
column sizing          format 999,999,999;
break on owner on tablespace_name;

ttitle ' List All Segments with More than 5 Extents '

select owner, tablespace_name, segment_name||
         decode(segment_type,'TABLE','[T]', 'INDEX', '[I]', 
                           'ROLLBACK','[R]', '[O]') segment_name
       , sum(bytes) sizing, 
   decode(count(*),1,to_char(count(*)),
		   2,to_char(count(*)),
		   3,to_char(count(*)),
		   4,to_char(count(*)),
		   5,to_char(count(*)),
		   to_char(count(*))||' < Re-build') seg_count
  from dba_extents
  group by owner, tablespace_name, segment_name||
         decode(segment_type,'TABLE','[T]', 'INDEX', '[I]',
             'ROLLBACK','[R]', '[O]')
  having count(*) > 5;

break on tablespace_name;

doc

The next figures list the free space on a per tablespace to assist
with the re-building of the table.

#

ttitle ' Show Free Space in Total By Tablespace'

select tablespace_name, sum(bytes) sizing 
  from dba_free_space
 group by tablespace_name;

ttitle ' List Free Extent Sizes per Tablespace'

select tablespace_name, bytes  sizing
  from user_free_space
  where bytes > 512000 
  order by tablespace_name, bytes desc;

doc

The following output lists the sizes of all datafiles being used by 
the database (excluding control files)

Make sure that your data files are evenly spread across disks from a 
performance perspective.


#

column file_name format a50;
column bytes format 999,999,999,999

ttitle ' Data File Sizes '

select file_name, bytes 
  from dba_data_files
/

column name format a14;
column "Total Free Space" format 99,999,999,999
column "Largest Free Extent" format 99,999,999,999
column "Default Initial Extent" format 9,999,999

ttitle 'Additional Extent Information'


select name, sum(length)*4096 "Total Free Space", 
       max(length)*4096 "Largest Free Extent",
       dflinit*4096 "Default Initial Extent"
from sys.fet$ a, sys.ts$ b
where a.ts# =b.ts#
group by name , dflinit
/

doc

The next output is not strictly tuning, but it gives you a list of all    
database objects that will fail when they attempt to throw their next extent
due to a lack of a free extent that is of sufficient size in the same
tablespace as where the object resides. If the problem happens to occur 
on a dictionary table, the whole database can potentially freeze, which I
suppose is response related. 


#

ttitle 'Database Objects that will have Trouble Throwing Extents'

column owner format a10;
column segment_name format a22;
column segment_type format a10;
column tablespace_name format a14;
column next_extent format 999,999,999;

SELECT seg.owner, seg.segment_name,
       seg.segment_type, seg.tablespace_name,
       t.next_extent
   FROM sys.dba_segments seg,
        sys.dba_tables   t
WHERE  (seg.segment_type = 'TABLE'
  AND    seg.segment_name = t.table_name
  AND    seg.owner        = t.owner
  AND    NOT EXISTS
            (select tablespace_name
               from dba_free_space free
              where free.tablespace_name =  t.tablespace_name
                and bytes               >=  t.next_extent     ))
UNION
SELECT seg.owner, seg.segment_name,
       seg.segment_type, seg.tablespace_name,
       DECODE (seg.segment_type,
               'CLUSTER',  c.next_extent)
   FROM sys.dba_segments seg,
        sys.dba_clusters c 
WHERE   (seg.segment_type = 'CLUSTER'
  AND    seg.segment_name = c.cluster_name
  AND    seg.owner        = c.owner
  AND    NOT EXISTS
            (select tablespace_name
               from dba_free_space free
              where free.tablespace_name =  c.tablespace_name
                and bytes               >=  c.next_extent     ))
UNION
SELECT seg.owner, seg.segment_name,
       seg.segment_type, seg.tablespace_name,
       DECODE (seg.segment_type,
               'INDEX',    i.next_extent )
   FROM sys.dba_segments seg,
        sys.dba_indexes  i
WHERE  (seg.segment_type = 'INDEX'
  AND    seg.segment_name = i.index_name
  AND    seg.owner        = i.owner
  AND    NOT EXISTS
            (select tablespace_name
               from dba_free_space free
              where free.tablespace_name =  i.tablespace_name
                and bytes               >=  i.next_extent     ))
UNION
SELECT seg.owner, seg.segment_name,
       seg.segment_type, seg.tablespace_name,
       DECODE (seg.segment_type,
               'ROLLBACK', r.next_extent)
   FROM sys.dba_segments seg,
        sys.dba_rollback_segs r
where  (seg.segment_type = 'ROLLBACK'
  AND    seg.segment_name = r.segment_name
  AND    seg.owner        = r.owner
  AND    NOT EXISTS
            (select tablespace_name
               from dba_free_space free
              where free.tablespace_name =  r.tablespace_name
                and bytes               >=  r.next_extent     )) 
/

doc

The following output lists latch contention. A latch is used to protect
data structures inside the SGA and provide fast access to the database         
buffers. A process that needs a latch has to own the latch which it must
access from a hash table. If a process has problems obtaining a latch, it
must wait until it is able to obtain one. Latch contention is more     
predominant on multi CPU applications. Spinning on a latch avoids the 
more costly operation of accessing the latch via conventional operating
system queueing mechanism.


The output from the following report which can be tuned are the redo 
latches.  The redo latches are the "redo allocation" and the "redo copy"
latches. The redo allocation latch serially writes all changes to the 
database to the log buffer. On a single CPU system, you cannot repair       
contention on this latch, and in fact latches should be minimal on a 
single CPU system. 

Latch contention tends to be much more evident on 
multi CPU machines. If you have a multi-CPU machine, you must set the 
INIT.ora parameter equal to the number of CPUs of your machine. Setting 
this parameter allows a second latch (copy latch) to access the log buffer. 

All changed data which is written to the log buffer which is larger than the
value specified in the parameter LOG_SMALL_ENTRY_MAX_SIZE is written to the
copy latch and all those smaller that or equal to the size use the redo
allocation latch. If you experience contention on the copy latch, you should
decrease the  LOG_SMALL_ENTRY_MAX_SIZE to force more entries through the
redo allocation latch. If the redo allocation latch has the problem, you
can increase  LOG_SMALL_ENTRY_MAX_SIZE to force more entries through the 
redo copy latch.

Another interesting latch contention figure is that for "cache buffer chains"
and "cache buffer lru chains". This is latch waits on buffer cache accesses.
The worst case I have seen on these waits was when a production DBA had 
decided to turn on DB_LRU_EXTENDED_STATISTICS and DB_BLOCK_LRU_STATISTICS. 
The DBA got some magnificent reports on the effect of increasing and 
decreasing the DB_BLOCK_BUFFERS (buffer cache) but the production users
weren't too happy with the significant response degradation. 

If you get latch contention on the a multi CPU computer, you can decrease
the spin_count. The default spin count on most machines is set to 200. I 
know of at least one benchmark on HP's where the spin_count was set to 1, 
to achieve optimal throughput. I have been told that on single CPU machines
you can increase a value "_Latch_Wait_postings" to say 20 and this will     
provide a similar response improvement. Note: the _ in front of the parameter
name indicates that it is a secret parameter. We are never totally sure if
the secret parameters work. They appear to be on again/ off again from one
version of Oracle to the next. See the output at the beginning of this report
for a full list of undocumented parameters. 

Speaking of undocumented parameters, we used to be able to decrease the
_DB_WRITER_MAX_SCAN_CNT and increase the _DB_BLOCK_WRITE_BATCH to avoid latch
contention. Whether the parameters take effect will vary from one version of
Oracle to the next. Try them out on your system only if you are experiencing
latch contention.

I've observed latch contention on the library cache. Stay tuned on how to
overcome this problem. I'll have to learn this one as well.

 
#

ttitle '  Latch Gets and Misses '

select substr(name,1,25), gets, misses, 
       immediate_gets, immediate_misses
  from v$latch
 where misses > 0
    or immediate_misses > 0
/

Doc

The following query shows the minimum and maximum times between log switches.
Typically, the minimum time should exceed a few minutes. The average figure
may be effected by no overnight activity taking place. If the switches are 
less than 30 seconds on a regular basis (see the second query), I would  
suggest that you should enlarge the size of the redo log files.

#



column "Min Minutes BW Checkpoints" format 999,999.99;
column "Avg Minutes BW Checkpoints" format 999,999.99;

ttitle 'Minimum and Average time Between Checkpoints'

select min(to_number(to_date(lh2.first_time,'mm/dd/yy hh24:mi:ss') 
              -    to_date(lh1.first_time,'mm/dd/yy hh24:mi:ss') )
                              * 24 * 60) "Min Minutes BW Checkpoints",
       avg(to_number(to_date(lh2.first_time,'mm/dd/yy hh24:mi:ss')
              -    to_date(lh1.first_time,'mm/dd/yy hh24:mi:ss') )
                              * 24 * 60) "Avg Minutes BW Checkpoints"
 from v$loghist lh1, v$loghist lh2
where lh1.sequence# + 1 = lh2.sequence#
  and lh1.sequence# <
             (select max (sequence#)
                from  v$loghist )
/

drop view min_bw_checkpoints;
create view  min_bw_checkpoints as
select to_number(to_date(lh2.first_time,'mm/dd/yy hh24:mi:ss') 
              -    to_date(lh1.first_time,'mm/dd/yy hh24:mi:ss') )
                              * 24 * 60      "Minutes BW Checkpoints"
 from v$loghist lh1, v$loghist lh2
where lh1.sequence# + 1 = lh2.sequence#
  and lh1.sequence# <
             (select max (sequence#)
                from  v$loghist )
/

column "Minutes BW Checkpoints" format 999,999.99

ttitle 'Minutes Between Checkpoints Listing Smaller Time Diffs First'

select "Minutes BW Checkpoints" 
  from  min_bw_checkpoints
  order by "Minutes BW Checkpoints"  asc
/

doc 


The following output assists you with tuning the LOG_BUFFER. The size of the
log buffer is set by assigning a value to the INIT.ora parameter LOG_BUFFER.

All changes are written to your redo logs via the log buffer. If your log 
buffer is too small it can cause excessive disk I/Os on the disks that 
contain your redo logs. The problem can be made worse if you have archiving
turned on because as well as writing to the redo logs, Oracle has to also 
read from the redo logs and copy the file to the archive logs. To overcome 
this problem, I suggest that you have 4 redo logs, typically 5 Meg or larger
in size  and alternate the redo logs from one disk to another, that  is
redo log 1 is on disk 1 , redo log 2 is on disk 2, redo log 3 is on disk 1
and redo log 4 is on disk 2.

This will ensure that the previous log being archived will be on a different
disk to the redo log being written to.

The following statistics also indicate inefficiencies with the log buffer
being too small. Typically a large site will have the LOG_BUFFER 500k or
larger.

The "redo log space wait time" indicates that the user process had to wait to 
get space in the redo file. This indicates that the current log buffer was
being written from and the process would have to wait. Enlarging the log 
buffer usually overcomes this problem. The closer the value is to zero, the
better your log buffer is tuned.

The "redo log space request" indicates the number of times a user process has
to wait for space in redo log buffer. It is often caused by the archiver being 
lazy and the log writer can't write from the log buffer to the redo log 
because the redo log has not been copied by the ARCH process. One possible
cause of this problem is where Hot Backups are taking place on files that are
being written to heavily. Note: for the duration of the hot backups, an 
entire block is written out to the log buffer and the redo logs for each
change to the database, as compared to just the  writing the characters that
have been modified.

There is a parameter _LOG_BLOCKS_DURING_BACKUP which is supposed to overcome
the Hot backup problem. It will pay to check if the parameter is functional
for your version of the RDBMS with Oracle. It can avoid severe bottlenecks.

A sensible approach for overnight processing is to time your Hot Backups, if
they are really required, (a lot of sites have them just for the sake of saying
that they are running them) to occur when the datafiles being backed up have
very little or preferably NO activity occurring against them.

The "redo buffer allocation retries" are where the redo writer is waiting for 
the log writer to complete the clearing out of all of the dirty buffers from 
the buffer cache. Only then, can the redo writer continue onto the next
redo log. This problem is usually caused by having the LOG_BUFFER parameter
too small, but can also be caused by having the buffer cache too small (see
the DB_BLOCK_BUFFERS parameter).

#

ttitle 'Extra LOG_BUFFER and Redo Log Tuning Information     '

select  substr(name, 1,25) , value 
  from v$sysstat
 where  name like 'redo%'                     
   and  value > 0
/

doc 


Oracle 7.1.5 introduced a new mechanism for improving the performance of
the shared pool area. When a user loads a large package or
procedure into the shared pool it has to search for large contiguous pieces
of memory. If there is not enough memory available, it has to make the 
free memory available. This is particularly damaging to performance.

The new mechanism introduced in 7.1.5 allows memory to be reserved to
satisfy loading in large packages/procedures without having too disruptive
an effect on the shared pool area performance. Smaller objects will not
be able to fragment the area because all objects smaller than the size 
specified in the parameter SHARED_POOL_RESERVED_MIN_ALLOC will be placed
into a shared pool area especially reserved for the smaller objects.

The total amount of space given to the larger area is specified by the 
parameter SHARED_POOL_RESERVED_SIZE.  The amount of space assigned to the
small objects is the SHARED_POOL_SIZE less the SHARED_POOL_RESERVED_SIZE.

There is also a new procedure that controls the amount of flushing from 
the shared pool to make way for new objects being moved into the pool. The 
RDBMS will continue to flush unused objects from the buffer pool until enough
free memory is available to fit the object into the shared pool. If there is
not enough available memory even after all of the objects have been flushed,
Oracle presents a 4031 error. The problem is that to get to a state of finding
that there is not enough memory can be particularly resource consuming.

The dbms_shared_pool.ABORT_REQUEST_THRESHOLD parameter sets the limit on
the size of objects allowed to flush the shared pool if the free space is
not sufficient to satisfy the request size. All objects larger than the 
setting (valid range is 5,000 to 2,147,483,647) will immediately return an
error 4031 is suuficient free space is not available. 

#

ttitle ' The Reserve Pool Settings for the Shared Pool Area'

select substr(name,1,32) "Parameter", substr(value,1,12) "Setting"
  from v$parameter
 where name like '%reser%'
/

set heading off

column next_line format a60 newline

ttitle ' Shared Pool Reserved Size Recommendation'

select 'You may need to increase the SHARED_POOL_RESERVED_SIZE' next_line,
       'Request Failures = '||request_failures
  from v$shared_pool_reserved
 where request_failures > 0
   and 0 != ( select to_number(value) 
               from v$parameter 
              where name = 'shared_pool_reserved_size' )
;

select 'You may be able to decrease the SHARED_POOL_RESERVED_SIZE' next_line,
       'Request Failures = '||request_failures
  from v$shared_pool_reserved
 where request_failures < 5
   and 0 != ( select to_number(value) 
               from v$parameter 
              where name = 'shared_pool_reserved_size' )
;

ttitle ' Checking Locations of Database Files' 

select value
  from v$parameter
 where name = 'log_archive_dest'
UNION
select name 
  from v$datafile 
UNION
select member      
  from v$logfile 
/

column owner owner format a10;
column column_name format a25;
column "Characteristics" format a40;

break on owner on column_name;
ttitle ' Columns with Inconsistent Data Lengths '
  
select owner, column_name
      , table_name||' '||data_type||'('||
      decode(data_type, 'NUMBER', data_precision, data_length)||')' "Characteristics"
  from all_tab_columns 
where  (column_name, owner)
   IN
  (select column_name, owner
     from all_tab_columns
    group by column_name, owner
    having min(decode(data_type, 'NUMBER', data_precision, data_length))
            < max(decode(data_type, 'NUMBER', data_precision, data_length)) )
  and  owner not in ('SYS', 'SYSTEM')
  and  '&Check_column_lengths_yn' in ('Y','y', 'YES')
/

ttitle ' Listing all Invalid Objects '

select owner, object_type, object_name, status 
  from all_objects
 where status = 'INVALID'
ORDER BY owner, object_type, object_name
/

ttitle ' Listing all Triggers and their Status'

select table_name, trigger_name, status
  from all_triggers
 order by table_name, trigger_name
/

ttitle ' Listing all Pinned Packages '

select name, kept
  from v$db_object_cache
where  kept not like 'N%'
/

ttitle ' Tablespace Details '

select tablespace_name, initial_extent, next_extent, pct_increase
  from dba_tablespaces
/     

ttitle 'Users that Have The SYSTEM Tablespace as Their Default'

select username 
  from dba_users
where  default_tablespace = 'SYSTEM' OR temporary_tablespace='SYSTEM'
/

spool off

doc

      Now drop all of the temporary tables used for performance checking  


#
drop table tot_read_writes;
drop view Full_Table_Scans;
drop table bh_temp;
drop table user_hit_ratios;
drop view user_cursors;
drop view min_bw_checkpoints;

