Oracle Tuning Tips
Oracle Tuning Tips
By Puneet Goenka
1
Tuning Tips and Techniques
Oracle’s SQL is a very flexible language.
You can use many different SQL statements
to accomplish the same purpose.
Yet, although dozens of differently
constructed queries and retrieval statements
can produce the same result, in a given
situation only one statement will be the most
efficient choice.
2
It is much harder to write efficient SQL than it is to
write functionally correct SQL
3
Sharing SQL Statements
Parsing a SQL statement and figuring out its
optimal execution plan are time-consuming
operations, Oracle holds SQL statements in
memory after it has parsed them
Whenever you issue a SQL statement,
Oracle first looks in the context (SGA) area to
see if there is an identical statement there
To be shared, the SQL statements must truly
be the same
4
For example the following two select statements are NOT the same:
SELECT STUDENT_NMBER,
NAME
FROM STUDENT
WHERE STUDEN_NUMBER = ‘0220’
Select Student_Number,
Name
From Student
Where Student_Number = ‘0220’
5
Using Bind variables when possible
Try using Bind Variable instead of Literals. Consider the following SQL
statement –
SELECT FIRST_NAME, LAST_NAME
FROM Client
WHERE CLIENT_NUM = 1200
Since the CLIENT_NUMBER is likely to be different for every
execution, we will almost never find a matching statement in the
Shared Pool and consequently the statement will have to be reparsed
every time
Consider the following approach –
You do not need to create a new cursor or re-parse the SQL statement if the
value of the bind variable changes. Also, if another session executes the same
statement, it is likely to find them in the Shared Pool, since the name of the
bind variable does not change from execution to execution.
6
Using ROWID When Possible
Each record added to the database has a unique ROWID and will
ROWID points to the new location or the new ROWID and so on.
Use ROWID whenever possible to get the best performance out of
your retrievals
7
cursor accounts_cur is
select acct_no,
currency,
branch
Rowid acct_rowid,
…
…
From account
where . . . .
…
for acct_rec in accounts_cur loop
…
update account set …
…
where rowid = acct_rec.acct_rowid;
…
…
end loop;
8
Using WHERE in Place of HAVING
In general, avoid including a HAVING clause in the SELECT
statements. The HAVING clause filters selected rows only after all
rows have been fetched. This could include sorting, summing, and
etc. HAVING clause
usually used to filter a SELECT statement containing group
functions.
select *
from account
where cust_Active_flag = ‘y’
having group = ‘001’
Instead use -
select *
from account
where cust_Active_flag = ‘y’
and group = ‘001’
9
Using UNION ALL instead of UNION
10
Using NOT EXISTS in place of NOT IN for indexed
columns
In sub-query statements such as the following, the NOT IN
clause causes an internal sort/merge.
11
Using IN with MINUS in place of NOT IN for non
indexed columns
12
Using Joints in Place of EXISTS for Unique Scan Indexes and
small tables
In general join tables rather than specifying sub-queries for them such
as the following:
select acct_ID, currency, branch
from account
where exists (select 1 from branch where code =
branch and def_curr = '001')
With join -
select acct_ID,currency, branch
from account A, branch B
where b.code = A.branch
and A.def_curr = '001'
13
Influencing the Optimizer using HINTS
Hints are special instructions to Optimizer. You can change the
Optimization goal for an individual statement by using Hint. Some
commonly used Hints are: CHOOSE, RULE, FULL(table_name),
INDEX(table_name index_name), USE_NL,
USE_HASH(table_name), PARALLEL(table_name parallelism) etc.
In the above SQL statement, an Index Hint has been used to force
the use of a particular index.
14
Using Indexes to Improve Performance
Indexes primarily exist to enhance performance. But they do
not come without a cost. Indexes must be updated during
INSERT, UPDATE and DELETE operation, which may slow
down performance
Besides, the usefulness of an Index depends on selectivity of a
column/columns.
Generally Indexes are more selective if the column/columns
have a large number of unique values.
If an Index contains more than one column, it is called
CONCATENATED INDEX .
Concatenated index is often more selective than a single key
index.
Column positions play an important role in Concatenated
index. While using Concatenated Index, be sure to use
LEADING columns
15
Which is Faster: Indexed Retrieval or Full-table
Scan?
Full-table scans can be efficient because they require little disk
movement. The disk starts reading at one point and continues reading
contiguous data blocks.
Index retrievals are usually more efficient when retrieving few records
or when using joints with other tables.
If more than 52%, this percentage defers from table to table and
depends on the physical I/O, of the table retrieved a full table scan is
better.
16
Avoiding Calculations on Indexed Columns
The optimizer does not use an index if the indexed column is a part of
a function (in the WHERE clause). In general, avoid doing calculations
on indexed columns, apply function and concatenating on an indexed
columns.
Select * from
Account
Where substr(ac_acct_no,1,1) = ‘1’
Instead use -
Select * from
Account
Where ac_acct_no like ‘1%’
17
Avoiding NOT on Indexed Columns
When Oracle encounters a NOT, it will choose not to use index and
will perform a full-table scan instead.
For example the following select statement will never use the index on
STUDENT_NUM column
Select * from
student
Where STUDENT_NUM not like ‘9%’
18
Using UNION in Place of OR
19
Position of Joins in the WHERE Clause
Table joins should be written first before any condition of WHERE clause.
And the conditions which filter out the maximum records should be placed at
the end after the joins as the parsing is done from BOTTOM to TOP.
Least Efficient :
SELECT . . . .
FROM EMP E
WHERE SAL > 50000
AND JOB = ‘CLERK’
AND 25 < (SELECT COUNT(*)
FROM EMP WHERE MGR = E.EMPNO);
20
Most Efficient :
SELECT . . . .
FROM EMP E
WHERE 25 < (SELECT COUNT(*)
FROM EMP
WHERE MGR = E.EMPNO )
AND SAL > 50000 AND JOB =
‘CLERK’;
21
Side by Side Comparison of Join Methods
Nested Loops Join Sort-Merge Join Hash Join
Cluster Join
When can be used: Any join Equi joins only Equi joins only
Equi joins on
complete
cluster key of
clustered
tables only
Optimizer hint: use_nl Use_merge use_hash
None
Features:Works with any join Better than nested Better than nested
Reduces I/O for master-
loop when indesx is loop when index is
detail Queries
22
missing or search missing or search
ORACLE parser always processes table names from right to left, so
the table name you specify last (driving table) is actually the first
table processed.
If you specify more than one table in a FROM clause of a SELECT
statement, you must choose the table containing the lowest number
of rows as the driving table.
When ORACLE processes multiple tables, it uses an internal
sort/merge procedure to join those tables.
First, it scans and sorts the first table (the one specified last in the
FROM clause).
Next, it scans the second table (the one prior to the last in the FROM
clause) and merges all of the rows retrieved from the second table
with those retrieved from the first table.
For example:
Table TABA has 16,384 rows.
Table TABB has 1 row.
23
If three tables are being joined, select the intersection table as the driving
table.
The intersection table is the table that has many tables dependent on it.
E.g.. The EMP table represents the intersection between the LOCATION
table and the CATEGORY table.
SELECT . . .
FROM LOCATION L, CATEGORY C, EMP E
WHERE E.EMP_NO BETWEEN 1000 AND 2000
AND E.CAT_NO = C.CAT_NO
AND E.LOCN = L.LOCN
is more efficient than this next example:
SELECT . . .
FROM EMP E,
LOCATION L, CATEGORY C
WHERE E.CAT_NO = C.CAT_NO
AND E.LOCN = L.LOCN
AND E.EMP_NO BETWEEN 1000 AND 2000
24
Problems when Converting Index Column Types
25
But the following statement:
Select *
From acc_txn
Where acc_txn_ref_no = ‘119990012890’
26
Use DECODE to Reduce Processing
The DECODE statement provides a way to avoid having to
scan the same rows repetitively or to join the same table
repetitively.
For example:
SELECT COUNT(*), SUM(SAL)
FROM EMP
WHERE DEPT_NO = 0020
AND ENAME LIKE ‘SMITH%’;
You can achieve the same result much more efficiently with
DECODE:
27
SELECT COUNT(DECODE(DEPT_NO,0020, ‘X’, NULL))
D0020_COUNT,
COUNT(DECODE(DEPT_NO,0030,‘X’,NULL))
D0030_COUNT,
SUM(DECODE(DEPT_NO,0020, SAL, NULL))
D0020_SAL,
SUM(DECODE(DEPT_NO, 0030, SAL, NULL))
D0030_SAL
FROM EMP
WHERE ENAME LIKE ‘SMITH%’;
28
To improve performance, minimize the number of table
lookups in queries, particularly if your statements include
sub-query SELECTs or multi-column UPDATEs.
For example:
Least Efficient :
SELECT TAB_NAME
FROM TABLES
WHERE
TAB_NAME =
(SELECT TAB_NAME
FROM TAB_COLUMNS
WHERE VERSION = 604)
AND
DB_VER = (SELECT DB_VER
FROM TAB_COLUMNS
WHERE VERSION = 604)
29
Most Efficient :
SELECT TAB_NAME
FROM TABLES
WHERE (TAB_NAME,DB_VER)=
(SELECT TAB_NAME, DB_VER
FROM TAB_COLUMNS
WHERE VERSION = 604)
30
Use EXISTS in Place of DISTINCT
Avoid joins that require the DISTINCT qualifier on the
SELECT list when you submit queries used to determine
information at the owner end of a one-to-many relationship
(e.g. departments that have many employees).
Least Efficient :
SELECT DISTINCT DEPT_NO, DEPT_NAME
FROM DEPT D, EMP E
WHERE D.DEPT_NO = E.DEPT_NO
Most Efficient :
SELECT DEPT_NO, DEPT_NAME
FROM DEPT D
WHERE EXISTS (SELECT ‘X’
FROM EMP E
WHERE E.DEPT_NO = D.DEPT_NO);
32
Do Not Use:
Use
Select * from
Account
Where substr(ac_acct_no,1,1) = ‘9’
Use:
Select * from
Account
Where ac_acct_no like ‘9%’
Do Not Use:
Select *
From fin_trxn
Where ft_trxn_ref_no != 0
Use:
Select *
From fin_trxn
Where ft_trxn_ref_no > 0
33
Do Not Use:
Select *
From account
Where ac_type || ac_branch = ‘sav001’
Use:
Select *
From account
Where ac_type = ‘sav’
And ac_branch = ‘sav001’
Do Not Use:
Select *
From CLIENT where
to_char(CUTT_OFF_TIME,’yyyymmdd’) =
to_char(sysdate,’yyyymmdd’)
Use:
Select *
From CLIENT
Where CUT_OFF_DATE >=
trunc(sysdate) and CUT_OFF_TIME <
trunc(sysdate) + 1
34
Do Not Use:
Select *
From acct_trxn
Where to_char(at_value_date,’yyyymmdd’) >
to_char(sysdate,’yyyymmdd’)
Use:
Select *
From acct_trxn
Where at_value_date >= trunc(sysdate) + 1
35
Do Not Use:
Select *
From acct_trxn
Where to_char(at_value_date,’yyyymmdd’) <
to_char(sysdate,’yyyymmdd’)
Use:
Select *
From acct_trxn
Where at_value_date < trunc(sysdate)
36
Do Not Use:
Select *
From acct_trxn
Where to_char(at_value_date,’yyyymmdd’) >=
to_char(sysdate,’yyyymmdd’)
Use:
Select *
From acct_trxn
Where at_value_date >= trunc(sysdate)
37
Do Not Use:
Select *
From acct_trxn
Where to_char(at_value_date,’yyyymmdd’) <=
to_char(sysdate,’yyyymmdd’)
Use:
Select *
From acct_trxn
Where at_value_date < trunc(sysdate) + 1
Do Not Use:
Select count( *)
From BROKER
Use:
Select count(PRIMARY_KEY or a non null
INDEX column or 1 ) From Broker
38
Avoid Using SELECT * Clauses
The dynamic SQL column reference (*) gives you a way to refer to
all of the columns of a table.
The SQL parser handles all the field references by obtaining the
names of valid columns from the data dictionary and substitutes them
on the command line, which is time consuming.
39
Using SQL*Plus Autotrace
If you’re using SQL*Plus you can take advantage of the auto trace
feature to have queries explained automatically.
SQL*Plus will execute the query and display the execution plan following
the results.
E.g
SQL> SET AUTOTRACE ON EXPLAIN
SQL> SELECT animal_name FROM aquatic_animal
ORDER BY animal_name;
ANIMAL_NAME
------------------------------
Batty
Bopper
Flipper
3 rows selected.
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=3
Card=10
Bytes=170)
11 0 SORT (ORDER BY) (Cost=3 Card=10 Bytes=170)
41