This action might not be possible to undo. Are you sure you want to continue?
By Puneet Goenka
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.
It is much harder to write efficient SQL than it is to write functionally correct SQL A SQL choice is correct only if it produces the right result in the shortest possible amount of time, without impeding the performance of any other system resources.
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
For example the following two select statements are NOT the same: SELECT STUDENT_NMBER. Name From Student Where Student_Number = ‘0220’ 5 . NAME FROM STUDENT WHERE STUDEN_NUMBER = ‘0220’ Select Student_Number.
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 – SELECT FIRST_NAME. LAST_NAME FROM Client WHERE CLIENT_NUM = :Client_Num You do not need to create a new cursor or re-parse the SQL statement if the value of the bind variable changes.Using Bind variables when possible Try using Bind Variable instead of Literals. Consider the following SQL statement – SELECT FIRST_NAME. it is likely to find them in the Shared Pool. Also. if another session executes the same statement. since the name of the bind variable does not change from execution to execution. 6 . LAST_NAME FROM Client WHERE CLIENT_NUM = 1200 Since the CLIENT_NUMBER is likely to be different for every execution.
Use ROWID whenever possible to get the best performance out of your retrievals 7 . the original ROWID points to the new location or the new ROWID and so on.Using ROWID When Possible Each record added to the database has a unique ROWID and will never change until the delete statement issued on that record. If the record block or location was changed for any reason.
8 . … … From account where . . … for acct_rec in accounts_cur loop … update account set … … where rowid = acct_rec. . … … end loop. branch Rowid acct_rowid. currency. .acct_rowid.cursor accounts_cur is select acct_no.
HAVING clause usually used to filter a SELECT statement containing group functions. This could include sorting. and etc. summing. avoid including a HAVING clause in the SELECT statements. 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 WHERE in Place of HAVING In general. The HAVING clause filters selected rows only after all rows have been fetched.
The UNION operation sorts the result set to eliminate any rows.Using UNION ALL instead of UNION The SORT operation is very expensive in terms of CPU consumption. which are within the sub-queries. UNION ALL includes duplicate rows and does not require a sort. Unless you require that these duplicate rows be eliminated. use UNION ALL 10 .
STUDENT_NUM) 11 .Using NOT EXISTS in place of NOT IN for indexed columns In sub-query statements such as the following. select * from Student where STUDENT_NUM not in (select STUDENT_NUM from CLASS) So useselect * from STUDENT C where not exists (select 1 from CLASS A where A. the NOT IN clause causes an internal sort/merge.STUDENT_NUM = C.
Using IN with MINUS in place of NOT IN for non indexed columns In sub-query statements such as the following. the NOT IN clause causes an internal sort/merge select * from system_user where su_user_id not in (select ac_user from account) INSTEAD USE select * from system_user where su_user_id in (select su_user_id from system_user minus select ac_user from account) 12 .
code = A.branch and A. branch from account where exists (select 1 from branch where code = branch and def_curr = '001') With join select acct_ID. currency.def_curr = '001' 13 . branch B where b.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 A.
Influencing the Optimizer using HINTS Hints are special instructions to Optimizer. PARALLEL(table_name parallelism) etc. USE_NL. ACCT_ALLOCATION_PERCENTAGE FROM ACCOUNTS WHERE ACCOUNT_ID = 1200 The above SQL statement will be processed using the RULE based optimizer. Some commonly used Hints are: CHOOSE. USE_HASH(table_name). INDEX(table_name index_name). SELECT /*+ INDEX(A. ACCT_ID_IND) */ NAME. You can change the Optimization goal for an individual statement by using Hint. SELECT /*+RULE*/ NAME. FULL(table_name). an Index Hint has been used to force the use of a particular index. ACCT_ALLOCATION_PERCENTAGE FROM ACCOUNTS A WHERE ACCOUNT_ID = :ACCT_ID AND CLIENT_ID= :CLIENT_ID In the above SQL statement. 14 . RULE.
be sure to use LEADING columns 15 . Concatenated index is often more selective than a single key index. If an Index contains more than one column. Indexes must be updated during INSERT. it is called CONCATENATED INDEX .Using Indexes to Improve Performance Indexes primarily exist to enhance performance. Column positions play an important role in Concatenated index. Generally Indexes are more selective if the column/columns have a large number of unique values. which may slow down performance Besides. While using Concatenated Index. the usefulness of an Index depends on selectivity of a column/columns. But they do not come without a cost. UPDATE and DELETE operation.
Which is Faster: Indexed Retrieval or Full-table Scan? Full-table scans can be efficient because they require little disk movement. of the table retrieved a full table scan is better. The disk starts reading at one point and continues reading contiguous data blocks. If more than 52%. this percentage defers from table to table and depends on the physical I/O. Index retrievals are usually more efficient when retrieving few records or when using joints with other tables. 16 .
avoid doing calculations on indexed columns.1) = ‘1’ Instead use Select * from Account Where ac_acct_no like ‘1%’ Note : The SQL functions MIN and MAX are exceptions to this rule and will utilize all available indexes.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). Select * from Account Where substr(ac_acct_no. apply function and concatenating on an indexed columns. In general.1. 17 .
but not what is NOT in a table. 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 . it will choose not to use index and will perform a full-table scan instead. indexes are built on what is in a table. Remember. When Oracle encounters a NOT.Avoiding NOT on Indexed Columns In general avoid using NOT when testing indexed columns.
Using OR on an indexed column causes the optimizer to perform a full-table scan rather than an indexed retrieval.Using UNION in Place of OR In general. always consider the UNION verb instead of OR verb in the WHERE clauses. 19 .
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. .EMPNO). Least Efficient : SELECT .Position of Joins in the WHERE Clause Table joins should be written first before any condition of WHERE clause. . FROM EMP E WHERE SAL > 50000 AND JOB = ‘CLERK’ AND 25 < (SELECT COUNT(*) FROM EMP WHERE MGR = E. 20 . .
Most Efficient : SELECT . . . . 21 .EMPNO ) AND SAL > 50000 AND JOB = ‘CLERK’. FROM EMP E WHERE 25 < (SELECT COUNT(*) FROM EMP WHERE MGR = E.
ora parameters:None None hash_area_size sort_area_size hash_join_enabled db_file_multi block_ read_count hash_multiblock_io_count Features:Works with any join I/O for masterdetail Queries Better than nested loop when indesx is missing or search critiria is not restrictive Better than nested Reduces loop when index is missing or search criteria is not restrictive 22 .Side by Side Comparison of Join Methods Join When can be used: joins on complete cluster key of clustered Optimizer hint: use_nl Any join Equi joins only Equi joins only Equi Nested Loops Join Sort-Merge Join Hash Join Cluster tables only Use_merge Temporary segments use_hash Memory None Storage Disk I/O Resource concerns: CPU init.
TABA 0. SELECT COUNT(*) FROM TABA. it scans and sorts the first table (the one specified last in the FROM clause). ORACLE parser always processes table names from right to left.09 seconds elapsed 23 . When ORACLE processes multiple tables. If you specify more than one table in a FROM clause of a SELECT statement.96 seconds elapsed 26. For example: Table TABA has 16. you must choose the table containing the lowest number of rows as the driving table. so the table name you specify last (driving table) is actually the first table processed. First. it uses an internal sort/merge procedure to join those tables.384 rows. 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. TABB SELECT COUNT(*) FROM TABB. Table TABB has 1 row.
CAT_NO = C. E.g.CAT_NO = C. .LOCN is more efficient than this next example: SELECT . FROM LOCATION L. The intersection table is the table that has many tables dependent on it. EMP E WHERE E.CAT_NO AND E. . CATEGORY C. CATEGORY C WHERE E. FROM EMP E. select the intersection table as the driving table.LOCN = L. LOCATION L. SELECT . .CAT_NO AND E..LOCN = L.EMP_NO BETWEEN 1000 AND 2000 AND E. If three tables are being joined.LOCN AND E. . The EMP table represents the intersection between the LOCATION table and the CATEGORY table.EMP_NO BETWEEN 1000 AND 2000 24 .
when it compares columns of different type. because of conversion this statement will actually be processed as: Select * from Account Where to_number(ACCOUNT_ID) = 90426001 25 . the character column automatically has its type converted to numeric. or casting. Select * from Account Where ACCOUNT_ID = 90426001 In fact.Problems when Converting Index Column Types Oracle performs simple column type conversion. If a numeric column is compared to an alphabetic column.
But the following statement: Select * From acc_txn Where acc_txn_ref_no = ‘119990012890’ Will be processed as: Select * From acc_txn Where acc_txn_ref_no = to_number(‘119990012890’ ) 26 .
SELECT COUNT(*). You can achieve the same result much more efficiently with DECODE: 27 .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. SUM(SAL) FROM EMP WHERE DEPT_NO = 0030 AND ENAME LIKE ‘SMITH%’. For example: SELECT COUNT(*). SUM(SAL) FROM EMP WHERE DEPT_NO = 0020 AND ENAME LIKE ‘SMITH%’.
Similarly.0020.NULL)) D0030_COUNT. 0030. NULL)) D0030_SAL FROM EMP WHERE ENAME LIKE ‘SMITH%’. NULL)) D0020_SAL. SAL.0030. NULL)) D0020_COUNT.SELECT COUNT(DECODE(DEPT_NO. 28 . COUNT(DECODE(DEPT_NO. SUM(DECODE(DEPT_NO.‘X’. SUM(DECODE(DEPT_NO. DECODE can be used in GROUP BY or ORDER BY clause effectively. SAL.0020. ‘X’.
To improve performance. 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 . particularly if your statements include sub-query SELECTs or multi-column UPDATEs. minimize the number of table lookups in queries.
DB_VER)= (SELECT TAB_NAME. DB_VER FROM TAB_COLUMNS WHERE VERSION = 604) 30 .Most Efficient : SELECT TAB_NAME FROM TABLES WHERE (TAB_NAME.
DEPT_NAME FROM DEPT D WHERE EXISTS (SELECT ‘X’ FROM EMP E WHERE E.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.DEPT_NO = D. departments that have many employees).DEPT_NO = E. EMP E WHERE D. 31 . EXISTS is a faster alternative because the RDBMS kernel realizes that when the sub-query has been satisfied once. DEPT_NAME FROM DEPT D.DEPT_NO).DEPT_NO Most Efficient : SELECT DEPT_NO. Least Efficient : SELECT DISTINCT DEPT_NO.g. the query can be terminated.
an alternative approach. For each clause that cannot use an index.Some Do’s and Don’ts Some SELECT statement WHERE clauses do not use indexes at all. If you have specified an index over a table that is referenced by a clause of type shown in this section Oracle will simply ignore the index. which will allow you to get better performance out of your SELECT statements is suggested. 32 .
Do Not Use: Select * from Account Where substr(ac_acct_no.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 .1.
’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 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.
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 .
which is time consuming. 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. 39 . Do not use the * feature because it is very inefficient -.Avoid Using SELECT * Clauses The dynamic SQL column reference (*) gives you a way to refer to all of the columns of a table.the * has to be converted to each column in turn.
E.Using SQL*Plus Autotrace If you’re using SQL*Plus you can take advantage of the auto trace feature to have queries explained automatically. Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=10 Bytes=170) 1 0 SORT (ORDER BY) (Cost=3 Card=10 Bytes=170) 2 1 TABLE ACCESS (FULL) OF ‘AQUATIC_ANIMAL’ (Cost=1 Card=10 Bytes=170) 40 . ANIMAL_NAME -----------------------------Batty Bopper Flipper 3 rows selected.g SQL> SET AUTOTRACE ON EXPLAIN SQL> SELECT animal_name FROM aquatic_animal ORDER BY animal_name. SQL*Plus will execute the query and display the execution plan following the results.
If a query generates a lot of I/O and consumes a lot of CPU. 41 . In that case use following : SQL> SET AUTOTRACE TRACEONLY EXPLAIN you are through using autotrace. SQL*Plus does execute the query. you won’t want to kick it off just to see the execution plan. you can turn the feature off by issuing the SET AUTOTRACE OFF command.
This action might not be possible to undo. Are you sure you want to continue?
We've moved you to where you read on your other device.
Get the full title to continue reading from where you left off, or restart the preview.