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":
- partial schema statistics (using dbms_stats)
- missing indexes
- optimizer_index_cost_adj=15
#10-15 for OLTP systems, 50 for DW #This adjusts the optimizer to
favor index access
- optimizer_index_caching=85 (depending on RAM for index caching,
around 85)
- optimizer_mode=first_rows (for OLTP)
- parallel_automatic_tuning=TRUE (parallelizes full-table scans,
Because parallel full-table scans are very fast, the CBO will give a
higher cost to index access and be friendlier to full-table scans)
- hash_area_size too small (too many nested loop joins)
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