Tuning Methodology

Quick thinks to check for
Check Disk I/O
Improper PGA Setup
Modify init.ora Parameters
SQL Code Tuning
Collect Schema Statistics
Redo Log Switches
Large Full Table Scans
Small Full Table Scans and Index Scans
Many Indexes on Data Buffer Cache
Check for skewed Indexes (unbalanced)
Tuning Database Buffer Cache
Fragmentation on DB Objects
Size of LOG_BUFFER
Size of SHARED_POOL_SIZE
Allocate Files Properly (check waits on them)
Checking Active Statements
Use IPC for local Connections
Check Undo Parameters
Detect High SQL Parse
Monitor Open and Cached Cursors
Detect Top 10 Queries in SQL Area
Allocate Objects into Multiple Block Buffers (another web page)
Check for Indexes not Used and HOT Tables
Detect and Resolve Buffer Busy Waits    ***********************
Show Porcentage of a Table in the data buffer
Testing Procedures or Packages for Performance
Using PGA Advice Utility
Check Sorts
Optimizing Indexes (creating 32k block size)


Quick Things to Check for
My goal is to quickly identify and correct performance problems.  Here is a summary of the things that I look at first:
1 - Install STATSPACK first, and get hourly snaps working.
 
2 - Get an SQL access report (or plan9i.sql), an spreport during peak times, and statspack_alert.sql output.
 
3 -  Look for "silver bullet fixes": 
 4 - Fully utilize server RAM - On a dedicated Oracle server, use all extra RAM for db_cache_size less PGA's and 20% RAM reserve for OS.
 
5 - Get the bottlenecks - See STATSPACK top 5 wait events - OEM performance pack reports - TOAD reports
 
6 - Look for Buffer Busy Waits resulting from table/index freelist shortages
 
7 - See if large-table full-table scans can be removed with well-placed indexes
 
8 - If tables are low volatility, seek an MV that can pre-join/pre-aggregate common queries.  Turn-on automatic query rewrite
 
9 - Look for non-reentrant SQL - (literals values inside SQL from v$sql) - If so, set cursor_sharing=force


Non-Use of Bind Variables
A quick method of seeing whether code is being reused (a key indicator of proper bind variable usage) is to look at the values of reusable and non-reusable memory in the shared pool. A SQL for determining this comparison of reusable to non-reusable code is shown here:
ttitle 'Shared Pool Utilization'
spool sql_garbage
select 1 nopr, to_char(a.inst_id) inst_id, a.users users,
       to_char(a.garbage,'9,999,999,999') garbage,
       to_char(b.good,'9,999,999,999') good,
       to_char((b.good/(b.good+a.garbage))*100,'9,999,999.999') good_percent
       from (select a.inst_id, b.username users,
                    sum(a.sharable_mem+a.persistent_mem) Garbage,
                    to_number(null) good
              from sys.gv_$sqlarea a,dba_users b
              where (a.parsing_user_id = b.user_id and a.executions<=1)
              group by a.inst_id, b.username
             union
              select distinct c.inst_id, b.username users, to_number(null) garbage,
                       sum(c.sharable_mem+c.persistent_mem) Good
                from dba_users b, sys.gv_$sqlarea c
                where (b.user_id=c.parsing_user_id and c.executions>1)
                group by c.inst_id, b.username) a,
            (select a.inst_id, b.username users, sum(a.sharable_mem+a.persistent_mem) Garbage,
                    to_number(null) good
               from sys.gv_$sqlarea a, dba_users b
               where (a.parsing_user_id = b.user_id and a.executions<=1)
               group by a.inst_id,b.username
             union
              select distinct c.inst_id, b.username users, to_number(null) garbage,
                     sum(c.sharable_mem+c.persistent_mem) Good
                from dba_users b, sys.gv_$sqlarea c
                where (b.user_id=c.parsing_user_id and c.executions>1)
                group by c.inst_id, b.username) b
where a.users=b.users
  and a.inst_id=b.inst_id
  and a.garbage is not null and b.good is not null
union
select 2 nopr,
'-------' inst_id,'-------------' users,'--------------' garbage,'--------------' good,
'--------------' good_percent from dual
union
select 3 nopr, to_char(a.inst_id,'999999'), to_char(count(a.users)) users,
       to_char(sum(a.garbage),'9,999,999,999') garbage, to_char(sum(b.good),'9,999,999,999') good,
       to_char(((sum(b.good)/(sum(b.good)+sum(a.garbage)))*100),'9,999,999.999') good_percent
  from (select a.inst_id, b.username users, sum(a.sharable_mem+a.persistent_mem) Garbage,
               to_number(null) good
          from sys.gv_$sqlarea a, dba_users b
          where (a.parsing_user_id = b.user_id and a.executions<=1)
          group by a.inst_id,b.username
       union
        select distinct c.inst_id, b.username users, to_number(null) garbage,
               sum(c.sharable_mem+c.persistent_mem) Good
          from dba_users b, sys.gv_$sqlarea c
          where (b.user_id=c.parsing_user_id and c.executions>1)
          group by c.inst_id,b.username) a,
        (select a.inst_id, b.username users, sum(a.sharable_mem+a.persistent_mem) Garbage,
                to_number(null) good
          from sys.gv_$sqlarea a, dba_users b
          where (a.parsing_user_id = b.user_id and a.executions<=1)
          group by a.inst_id,b.username
       union
         select distinct c.inst_id, b.username users, to_number(null) garbage,
                sum(c.sharable_mem+c.persistent_mem) Good
           from dba_users b, sys.gv_$sqlarea c
           where (b.user_id=c.parsing_user_id and c.executions>1)
           group by c.inst_id, b.username) b
   where a.users=b.users
     and a.inst_id=b.inst_id
     and a.garbage is not null and b.good is not null
   group by a.inst_id
   order by 1,2 desc
/
spool off
ttitle off
set pages 22


An example report is
Date: 03/25/05                                              Page:   1
Time: 17:51 PM            Shared Pool Utilization           SYSTEM
                            whoville database
users                Non-Shared SQL Shared SQL     Percent Shared
-------------------- -------------- -------------- --------------
WHOAPP                  532,097,982      1,775,745           .333
SYS                       5,622,594      5,108,017         47.602
DBSNMP                      678,616        219,775         24.463
SYSMAN                      439,915      2,353,205         84.250
SYSTEM                      425,586         20,674          4.633
-------------        -------------- -------------- --------------
5                       541,308,815      9,502,046          1.725


As you can see the majority owner in this application, WHOAPP is only showing 0.3 percent of reusable code by memory usage and is tying up an amazing 530 megabytes with non-reusable code! Let’s look at a database with good reuse statistics. Look at this one:
Date: 11/13/05                                              Page:   1
Time: 03:15 PM            Shared Pool Utilization           PERFSTAT      
                            dbaville database                                 
                                                                          
users                Non-Shared SQL Shared SQL     Percent Shared         
-------------------- -------------- -------------- --------------         
DBAVILLAGE                9,601,173     81,949,581         89.513         
PERFSTAT                  2,652,827        199,868          7.006         
DBASTAGER                 1,168,137     35,468,687         96.812         
SYS                          76,037      5,119,125         98.536         
-------------        -------------- -------------- --------------         
4                        13,498,174    122,737,261         90.092 

Notice how the two application owners, DBAVILLAGE and DBASTAGER show 89.513 and 96.812 reuse percentage by memory footprint for code.

So what else can we look at to see about code reusage, the above reports give us a gross indication, how about something with a bit more usability to correct the situation? The V$SQLAREA and V$SQLTEXT views give us the capability to look at the current code in the shared pool and determine if it is using, or not using bind variables.
set lines 140 pages 55 verify off feedback off
col num_of_times heading 'Number|Of|Repeats'
col SQL heading 'SubString - &&chars Characters'
col username format a15 heading 'User'
@title132 'Similar SQL'
spool rep_out\&db\similar_sql&&chars
select b.username,substr(a.sql_text,1,&&chars) SQL,
       count(a.sql_text) num_of_times from v$sqlarea a, dba_users b
where a.parsing_user_id=b.user_id
group by b.username,substr(a.sql_text,1,&&chars) having count(a.sql_text)>&&num_repeats
order by count(a.sql_text) desc;
spool off
undef chars
undef num_repeats
clear columns
set lines 80 pages 22 verify on feedback on
ttitle off


It shows a simple script to determine, based on the first x characters (input when the report is executed) the number of SQL statements that are identifical up to the first x characters. This shows us the repeating code in the database and helps us to track down the offending statements for correction. An example output :

Date: 02/23/05                                         Page:   1            

Time: 10:20 AM              Similar SQL               SYSTEM       
                          whoville database                                                               
User            SubString - 120 Characters                                                                                                 
--------------- -------------------------------------------------------
 Number                                                                                                                                
 Of                                                                                                                                 
 Repeats                                                                                                                                
----------                                                                                                                                
WHOAPP         SELECT Invoices."INVOICEKEY", Invoices."CLIENTKEY", Invoices."BUYSTATUS", Invoices."DEBTORKEY", Invoices."INPUTTRANSKEY"   1752                                                                                                                                
WHOAPP         SELECT DisputeCode.DisputeCode , DisputeCode.Disputed , InvDispute."ROWID" , DisputeCode."ROWID"  FROM InvDispute , Disp   458                                                                                                                                
WHOAPP         SELECT Transactions.PostDate , Payments.PointsAmt , Payments.Type_ AS PmtType , Payments.Descr , Payments.FeeBasis , Pay   449                                                                                                                                
SYS             SELECT SUM(Payments.Amt) AS TotPmtAmt , SUM(Payments.FeeEscrow) AS TotFeeEscrow , SUM(Payments.RsvEscrow) AS TotRsvEscro 428                                                                                                                                
WHOAPP         SELECT SUM(Payments.Amt) AS TotPmtAmt, SUM(Payments.FeeEscrow) AS TotFeeEscrow, SUM(Payments.RsvEscrow) AS TotRsvEscrow 428                                                                                                                                 
WHOAPP         SELECT Transactions.BatchNo , Payments.Amt , Payments."ROWID" , Transactions."ROWID"  FROM Payments , Transactions WHERE 396                                                                                                                                 
WHOAPP         INSERT INTO Payments (PaymentKey, AcctNo, Amt, ChargeAmt, Descr, FeeBasis, FeeEarned, FeeEscrow, FeeRate, FeeTaxAmt, Hol 244                                                                                                                                 
WHOAPP         SELECT Clients.Name , Clients.ClientNo , Invoices.InvNo , Invoices.ClientKey AS InvClientKey , Transactions.ClientKey AS     244                                                                                                                                 
SYS             SELECT COUNT(*) AS RecCount , INVOICES."ROWID" , TRANSACTIONS."ROWID" , PROGRAMS."ROWID"  FROM INVOICES , TRANSACTIONS , 232                                                                                                                                 


Using a substring from the above SQL the V$SQLTEXT view can be used to pull an entire listing of the code

The proper fix for non-bind variable usage is to re-write the application to use bind variables. This of course can be an expensive and time consuming process, but ultimately it provides the best fix for the problem. However, what if you can’t change the code? Oracle has provided the CURSOR_SHARING initialization variable that will automatically replace the literals in your code with bind variables. The settings for CURSOR_SHARING are EXACT (the default), FORCE, and SIMILAR.

·        EXACT – The statements have to match exactly to be reusable

·        FORCE – Always replace literals

·        SIMILAR – Perform literal peeking and replace when it makes sense

We usually suggest the use of the SIMILAR option for CURSOR_SHARING



Improper Index Usage
You will be happy to know that starting with Oracle9i there is a new view that keeps the explain plans for all current SQL in the shared pool, this view, appropriately named V$SQL_PLAN allows DBAs to determine exactly what statements are using full table scans and more importantly how often the particular SQL statements are being executed
col object_name format a28
col rows|blocks|pool a30
set pages 55
set linesize 140
set trims on
ttitle 'Full Table - Index Scans'
spool Full_Table-Index_Scans.txt
select sp.object_name,
      (select executions from v$sqlarea sa
         where sa.address = sp.address
           and sa.hash_value =sp.hash_value) no_of_full_scans,
      (select trim(lpad(nvl(trim(to_char(num_rows)),' '),10,' ')||' | '||lpad(nvl(trim(to_char(blocks)),' '),10,' ')||' | '||buffer_pool)
         from dba_tables where table_name = sp.object_name
          and owner = sp.object_owner) "rows|blocks|pool",
      (select sql_text from v$sqlarea sa
         where sa.address = sp.address
           and sa.hash_value =sp.hash_value) sqltext
  from v$sql_plan sp
  where operation IN ('TABLE ACCESS','INDEX')
    and options in ('FULL','FULL SCAN','FAST FULL SCAN','SKIP SCAN','SAMPLE FAST FULL SCAN') 
    and object_owner IN ('XGUARD935')
and rownum < 60
order by 2 desc,3 desc;
spool off
set pages 20
ttitle off



Notice that I didn’t limit myself to just full table scans, I also looked for expensive index scans as well. The Report shows:

Fri Aug 24                                                                                                                         page    1
                                                          Full Table - Index Scans

OBJECT_NAME                  NO_OF_FULL_SCANS rows|blocks|pool
---------------------------- ---------------- ---------------------------------
SQLTEXT
--------------------------------------------------------------------------------------------------------------------------------------------
LOOKUP_WORKTYPE                        956170 17 |          5 | DEFAULT
SELECT WORKTYPEID FROM LOOKUP_WORKTYPE WHERE WORKTYPECODE = :B1

ROUTINGNUMBER                          294118 520 |          5 | DEFAULT
SELECT ROUTINGNUMBERID, ROUTINGNUMBER, BANKID, CENTERID FROM ROUTINGNUMBER WHERE BANKID = :B1

EXCHANGEITEMEXCEPTION                   39421 72280 |       1566 | DEFAULT
SELECT COUNT(1) FROM EXCHANGEITEMQUERY EIQU, EXCHANGEITEMEXCEPTION EIEX WHERE :B1 =EIQU.EXCHANGEITEMID AND EIQU.EXCHANGEITEMQUERYID=EIEX.EXC
HANGEITEMQUERYID AND EIEX.REMOVED = 0

ANDOR                                    3454 20 |          5 | DEFAULT
SELECT ANDORID, EXCEPTIONID, ISAND, LEFTID, RIGHTID FROM ANDOR ORDER BY EXCEPTIONID, ANDORID

EXCEPTIONS                               3377 97 |         60 | DEFAULT
SELECT E.EXCEPTIONID, EXCEPTIONNAME, DESCRIPTION, EXCEPTIONCODE, E.CENTERID, E.BANKID, E.CUSTOMERID, E.ACCOUNTID, DATASOURCEID, DATAFIELDID,
 INEQUALITYID, CONSTRAINTDATASOURCEID, CONSTRAINTDATAVALUE, D.DEFINITIONID, DEFINITIONATTRIBUTEID, E.ACTIVESTATUSID, E.APPLICATIONID, ISUSER
DEFINED FROM EXCEPTIONS E, DEFINITION D WHERE E.APPLICATIONID = :B1 AND E.EXCEPTIONID = D.EXCEPTIONID (+) ORDER BY E.EXCEPTIONNAME, D.DEFINI
TIONID

X937USERRECORD                           3317 0 |          1 | DEFAULT
INSERT INTO X937USERRECORD_ARCH SELECT * FROM X937USERRECORD WHERE OUTJOBID = :B1

UN_CENTERNAME                            1679
SELECT CENTERID, CENTERNAME, ACTIVESTATUSID AS CENTERACTIVESTATUSID, COMMENTS AS CENTERCOMMENTS, ITEMSETTINGID AS CENTERITEMSETTINGID, CENTE
RCODE, EXPORTSTATUSID AS CENTEREXPORTSTATUSID, EXPORTTIME AS CENTEREXPORTTIME, GLACCOUNTNUMBER, NULL AS BANKID FROM CENTER ORDER BY CENTERNA
ME

MACHINE                                  1481 3 |          5 | DEFAULT
SELECT M.MACHINEID, MACHINENAME, IPADDRESS, S.SERVICEID, SERVICENAME, APPLICATIONID FROM SERVICE S, MACHINE M, PROCESS P WHERE S.SERVICEID =
 P.SERVICEID AND M.MACHINEID = P.MACHINEID ORDER BY MACHINENAME, SERVICENAME

Notice instead of trying to capture the full SQL statement I just grab the HASH value.
I can then use the hash value to pull the interesting SQL statements using SQL similar to:

select sql_text
from v$sqltext
where hash_value=&hash
order by piece;

Once I see the SQL statement I use SQL similar to this to pull the table indexes:

set lines 132
col index_name form a30
col table_name form a30
col column_name format a30
select a.table_name,a.index_name,a.column_name,b.index_type
  from dba_ind_columns a, dba_indexes b
  where a.table_name =upper('&tab')
    and a.table_name=b.table_name
    and a.index_owner=b.owner
    and a.index_name=b.index_name
  order by a.table_name,a.index_name,a.column_position;
set lines 80

 Once I have both the SQL and the indexes for the full scanned table I can usually quickly come to a tuning decision if any additional indexes are needed or, if an existing index should be used. In some cases there is an existing index that could be used of the SQL where rewritten. In that case I will usually suggest the SQL be rewritten. An example extract from a SQL analysis of this type is shown here:

SQL> @get_it
Enter value for hash: 605795936
SQL_TEXT
----------------------------------------------------------------
DELETE FROM BOUNCE WHERE UPDATED_TS < SYSDATE - 21
 
SQL> @get_tab_ind
Enter value for tab: bounce
TABLE_NAME   INDEX_NAME                 COLUMN_NAME    INDEX_TYPE
------------ -------------------------- -------------- ----------
BOUNCE       BOUNCE_MAILREPRECJOB_UNDX  MAILING_ID     NORMAL
BOUNCE       BOUNCE_MAILREPRECJOB_UNDX  RECIPIENT_ID   NORMAL
BOUNCE       BOUNCE_MAILREPRECJOB_UNDX  JOB_ID         NORMAL
BOUNCE       BOUNCE_MAILREPRECJOB_UNDX  REPORT_ID      NORMAL
BOUNCE       BOUNCE_PK                  MAILING_ID     NORMAL
BOUNCE       BOUNCE_PK                  RECIPIENT_ID   NORMAL
BOUNCE       BOUNCE_PK                  JOB_ID         NORMAL

As you can see here there is no index on UPDATED_TS

SQL> @get_it
Enter value for hash: 3347592868

SQL_TEXT
----------------------------------------------------------------
SELECT VERSION_TS, CURRENT_MAJOR, CURRENT_MINOR, CURRENT_BUILD,
CURRENT_URL, MINIMUM_MAJOR, MINIMUM_MINOR, MINIMUM_BUILD, MINIMU
M_URL, INSTALL_RA_PATH, HELP_RA_PATH FROM CURRENT_CLIENT_VERSION


Here there is no WHERE clause, hence a FTS is required.

SQL> @get_it
Enter value for hash: 4278137387
 
SQL_TEXT
----------------------------------------------------------------
SELECT STATUS FROM DB_STATUS WHERE DB_NAME = 'ARCHIVE'
 
 
SQL> @get_tab_ind
Enter value for tab: db_status
 
Improper Memory Configuration
In this section we will discuss two major areas of memory, the database buffer area and the shared pool area. The PGA areas are discussed in a later section.

The Database Buffer Area

Anything that goes to users or gets into the database must go through the database buffers.
Gone are the days of a single buffer area (the default) now we have 2, 4, 8,, 16, 32 K buffer areas, keep and recycle buffer pools on top of the default area. Within these areas we have the consistent read, current read, free, exclusive current, and many other types of blocks that are used in Oracle’s multi-block consistency model.
The V$BH view (and it’s parent the X$BH table) are the major tools used by the DBA to track block usage, however, you may find that the data in the V$BH view can be misleading unless you also tie in block size data.

set pages 50
ttitle80 'All Buffers Status'
spool All_Buffers_Status.txt
select '32k '||status as status,  count(*) as num
  from v$bh
  where file# in(select file_id
                  from dba_data_files
                  where tablespace_name in ( select tablespace_name
                                               from dba_tablespaces
                                               where block_size=32768))
  group by '32k '||status
union
select '16k '||status as status, count(*) as num
  from v$bh where file# in(select file_id
                            from dba_data_files
                            where tablespace_name in (select tablespace_name
                                                        from dba_tablespaces
                                                        where block_size=16384))
  group by '16k '||status
union
select '8k '||status as status, count(*) as num
  from v$bh
  where file# in( select file_id
                    from dba_data_files
                    where tablespace_name in (select tablespace_name
                                                from dba_tablespaces
                                                where block_size=8192))
  group by '8k '||status
union
select '4k '||status as status, count(*) as num
  from v$bh
  where file# in(select file_id
                   from dba_data_files
                   where tablespace_name in ( select tablespace_name
                                                from dba_tablespaces
                                                where block_size=4096))
  group by '4k '||status
union
select '2k '||status as status, count(*) as num
  from v$bh
  where file# in(select file_id
                   from dba_data_files
                   where tablespace_name in ( select tablespace_name
                                                from dba_tablespaces
                                                where block_size=2048))
  group by '2k '||status
union
select status, count(*) as num
  from v$bh
  where status='free'
group by status
order by 1
/
spool off
ttitle off

As you can see, we will need to be SYS user to run it.  An example report would be:

Date: 12/13/05                                              Page:   1
Time: 10:39 PM               All Buffers Status             PERFSTAT      
                              whoville database                                 
                                                                          
STATUS           NUM                                                      
--------- ----------                                                       
32k cr          2930                                                      
32k xcur       29064                                                      
8k cr           1271                                                       
8k free            3                                                      
8k read            4                                                      
8k xcur       378747                                                      
free           10371               

As you can see, while there are free buffers, only 3 of them are available to the 8k, default area and none are available to our 32K area. The free buffers are actually assigned to a keep or recycle pool area (hence the null value for the blocksize) and are not available for normal usage.

So, if you see buffer busy waits, db block waits and the like and you run the above report and see no free buffers it is probably a good bet you need to increase the number of available buffers for the area showing no free buffers. You should not immediately assume you need more buffers because of buffer busy waits as these can be caused by other problems such as row lock waits, itl waits and other issues. Luckily Oracle10g has made it relatively simple to determine if we have these other types of waits:

-- Crosstab of object and statistic for an owner
--
col "Object" format a20
set numwidth 12
set lines 132
set pages 50
@title132 'Object Wait Statistics'
spool rep_out\&&db\obj_stat_xtab
select * from(
select DECODE(GROUPING(a.object_name), 1, 'All Objects',
      a.object_name) AS "Object",
sum(case when a.statistic_name = 'ITL waits'
then a.value else null end) "ITL Waits",
sum(case when a.statistic_name = 'buffer busy waits'
then a.value else null end) "Buffer Busy Waits",
sum(case when a.statistic_name = 'row lock waits'
then a.value else null end) "Row Lock Waits",
sum(case when a.statistic_name = 'physical reads'
then a.value else null end) "Physical Reads",
sum(case when a.statistic_name = 'logical reads'
then a.value else null end) "Logical Reads"
from v$segment_statistics a
where a.owner like upper('&owner')
group by rollup(a.object_name)) b
where (b."ITL Waits">0 or b."Buffer Busy Waits">0)
/
spool off
clear columns
ttitle off

This is an object statistic cross tab report based on the V$SEGMENT_STATISTICS view. The cross tab report generates a listing showing the statistics of concern as headers across the page rather than listings going down the page and summarizes them by object. This allows us to easily compare total buffer busy waits to the number of ITL or row lock waits. This ability to compare the ITL and row lock waits to buffer busy waits lets us see what objects may be experiencing contention for ITL lists, which may be experiencing excessive locking activity and through comparisons, which are highly contended for without the row lock or ITL waits. An example of the output of the report, edited for length, is shown here:

Date: 12/09/05                                     Page: 1
Time: 07:17 PM         Object Wait Statistics PERFSTAT
                whoville database
                                                                                                             
ITL Buffer Busy Row Lock Physical Logical
Object Waits       Waits    Waits     Reads Reads                     
-------------- ----- ----------- -------- ---------- -----------
BILLING 0 63636 38267 1316055 410219712
BILLING_INDX1 1 16510 55 151085 21776800
...
DELIVER_INDX1 1963 36096 32962 1952600 60809744
DELIVER_INDX2 88 16250 9029 18839481 342857488                       
DELIVER_PK 2676 99748 29293 15256214 416206384
DELIVER_INDX3 2856 104765 31710 8505812 467240320
...
All Objects 12613 20348859 1253057 1139977207 20947864752


In the above report the BILLING_INDX1 index has a large number of buffer busy waits but we can’t account for them from the ITL or Row lock waits, this indicates that the index is being constantly read and the blocks then aged out of memory forcing waits as they are re-read for the next process. On the other hand, almost all of the buffer busy waits for the DELIVER_INDX1 index can be attributed to ITL and Row Lock waits.
In situations where there are large numbers of ITL waits we need to consider the increase of the INITRANS setting for the table to remove this source of contention. If the predominant wait is row lock waits then we need to determine if we are properly using locking and cursors in our application (for example, we may be over using the SELECT…FOR UPDATE type code.) If, on the other hand all the waits are un-accounted for buffer busy waits, then we need to consider increasing the amount of database block buffers we have in our SGA.
As you can see, this object wait cross tab report can be a powerful addition to our tuning arsenal.
By knowing how our buffers are being used and seeing exactly what waits are causing our buffer wait indications we can quickly determine if we need to tune objects or add buffers, making sizing buffer areas fairly easy.
But what about the Automatic Memory Manager in 10g? It is a powerful tool for DBAs with systems that have a predictable load profile, however if your system has rapid changes in user and memory loads then AMM is playing catch up and may deliver poor performance as a result. In the case of memory it may be better to hand the system too much rather than just enough, just in time (JIT).
As many companies have found when trying the JIT methodology in their manufacturing environment it only works if things are easily predictable.
The AMM is utilized in 10g by setting two parameters, the SGA_MAX_SIZE and the SGA_TARGET. The Oracle memory manager will size the various buffer areas as needed within the range between base settings or SGA_TARGET and SGA_MAX_SIZE using the SGA_TARGET setting as an “optimal” and the SGA_MAX_SIZE as a maximum with the manual settings used in some cases as a minimum size for the specific memory component.


Check Disks I/O
Disk stress will show up on the Oracle side as excessive read or write times. Filesystem stress is shown by calculating the IO timings as shown here:

em Purpose: Calculate IO timing values for datafiles
col name format a65
col READTIM/PHYRDS heading 'Avg|Read Time' format 9,999.999
col WRITETIM/PHYWRTS heading 'Avg|Write Time' format 9,999.999
set lines 132 pages 45
start title132 'IO Timing Analysis'
spool rep_out\&db\io_time
select  f.FILE# ,d.name,PHYRDS,PHYWRTS,READTIM/PHYRDS,WRITETIM/PHYWRTS
  from v$filestat f, v$datafile d
  where f.file#=d.file#
    and phyrds>0 and phywrts>0
union
select  a.FILE# ,b.name,PHYRDS,PHYWRTS,READTIM/PHYRDS,WRITETIM/PHYWRTS
  from v$tempstat a, v$tempfile b
  where a.file#=b.file#
    and phyrds>0 and phywrts>0
order by 5 desc;
spool off
ttitle off
clear col

An example of the output : 

Date: 11/20/05                                                Page:   1
Time: 11:12 AM               IO Timing Analysis                PERFSTAT
                               whoraw database
 
FILE# NAME               PHYRDS PHYWRTS READTIM/PHYRDS WRITETIM/PHYWRTS
----- -------------- ---------- ------- -------------- ----------------
   13 /dev/raw/raw19      77751  102092     76.8958599       153.461829
   33 /dev/raw/raw35      32948   52764     65.7045041       89.5749375
    7 /dev/raw/raw90     245854  556242     57.0748615       76.1539869
   54 /dev/raw/raw84     208916  207539     54.5494409       115.610912
   40 /dev/raw/raw38       4743   27065     38.4469745       47.1722889
   15 /dev/raw/raw41       3850    7216     35.6272727       66.1534091
   12 /dev/raw/raw4      323691  481471     32.5510193       100.201424
   16 /dev/raw/raw50      10917   46483     31.9372538       74.5476626
   18 /dev/raw/raw24       3684    4909     30.8045603       71.7942554
   23 /dev/raw/raw58      63517   78160     29.8442779       84.4477866
    5 /dev/raw/raw91     102783   94639     29.1871516       87.8867909

As you can see we are looking at an example report from a RAW configuration using single disks. Notice how both read and write times exceed even the rather large good practice limits of 10-20 milliseconds for a disk read. However in my experience for reads you should not exceed 5 milliseconds and usually with modern buffered reads, 1-2 milliseconds. Oracle is more tolerant for write delays since it uses a delayed write mechanism, so 10-20 milliseconds on writes will normally not cause significant Oracle waits, however, the smaller you can get read and write times, the better!

For the money, I would suggest RAID0/1 or RAID1/0, that is, striped and mirrored. It provides nearly all of the dependability of RAID5 and gives much better write performance. You will usually take at least a 20 percent write performance hit using RAID5. For read-only applications RAID5 is a good choice, but in high-transaction/high-performance environments the write penalties may be too high.

Table 1 shows how Oracle suggests RAID should be used with Oracle database files.

RAID

Type of Raid

Control File

Database File

Redo Log File

Archive Log File

0

Striping

Avoid

OK

Avoid

Avoid

1

Shadowing

Best

OK

Best

Best

1+0

Striping and Shadowing

OK

Best

Avoid

Avoid

3

Striping with static parity

OK

OK

Avoid

Avoid

5

Striping with rotating parity

OK

Best if RAID0-1 not available

Avoid

Avoid

Table 1: RAID Recommendations (From Metalink NOTE: 45635.1)


Improper PGA setup
Oracle provides AWRRPT or statspack reports to track and show the number of sorts. Unfortunately hashes are not so easily tracked. Oracle tracks disk and memory sorts, number of sort rows and other sort related statistics. Hashes on the other hand only can be tracked usually by the execution plans for cumulative values, and by various views for live values. After 9i the parameter PGA_AGGREGATE_TARGET was provided to allow automated setting of the sort and hash areas. For currently active sorts or hashes the following script can be used to watch the growth of temporary areas.


column now format a14
column operation format a15
column dt new_value td noprint
set feedback off
select to_char(sysdate,'ddmonyyyyhh24miss') dt from dual;
set lines 132 pages 55
@title132 'Sorts and Hashes'
spool rep_out\&&db\sorts_hashes&&td
select sid,work_area_size,expected_size,actual_mem_used,max_mem_used,tempseg_size,
to_char(sysdate,'ddmonyyyyhh24miss') now, operation_type operation
from v$sql_workarea_active;
spool off
clear columns
set lines 80 feedback on
ttitle off


Example output from this report.

Date: 01/04/06                                                           Page:   1
Time: 01:27 PM                    Sorts and Hashes                       SYS
                                  whoville database
     Work Area Expected Actual Mem Max Mem Tempseg
SID       Size     Size       Used