Beruflich Dokumente
Kultur Dokumente
Remember, you must ALWAYS start with system-level SQL tuning, else later changes might
undo your tuned execution plans:
Optimize the server kernel - You must always tune your disk and network I/O
subsystem (RAID, DASD bandwidth, network) to optimize the I/O time, network packet
size and dispatching frequency.
Adjusting your optimizer statistics - You must always collect and store optimizer
statistics to allow the optimizer to learn more about the distribution of your data to take
more intelligent execution plans. Also, histograms can hypercharge SQL in cases of
determining optimal table join order, and when making access decisions on skewed
WHERE clause predicates.
Tune your SQL Access workload with physical indexes and materialized views - Just
as the 10g SQLAccess advisor recommends missing indexes and missing materialized
views, you should always optimize your SQL workload with indexes, especially functionbased indexes, a Godsend for SQL tuning.
11g Note: The Oracle 11g SQL Performance Analyzer (SPA), is primarily designed to speed up
the holistic SQL tuning process.
Once you create a workload (called a SQL Tuning Set, or STS), Oracle will repeatedly execute
the workload, using sophisticated predictive models (using a regression testing approach) to
accurately identify the salient changes to SQL execution plans, based on your environmental
changes. Using SPA, we can predict the impact of system changes on a workload, and we can
forecast changes in response times for SQL after making any change, like parameter changes,
schema changes, hardware changes, OS changes, or Oracle upgrades. For details, see the book
Oracle 11g New Features.
Once the environment, instance, and objects have been tuned, the Oracle administrator can focus
on what is probably the single most important aspect of tuning an Oracle database: tuning the
individual SQL statements. In this final article in my series on Oracle tuning, I will share some
general guidelines for tuning individual SQL statements to improve Oracle performance.
Oracle SQL tuning goals
Oracle SQL tuning is a phenomenally complex subject. Entire books have been written about the
nuances of Oracle SQL tuning; however, there are some general guidelines that every Oracle
DBA follows in order to improve the performance of their systems. Again, see the book "Oracle
Tuning: The Definitive Reference", for complete details.
The goals of SQL tuning focus on improving the execution plan to fetch the rows with the
smallest number of database "touches" (LIO buffer gets and PIO physical reads).
b-tree indexes can be added to tables, and bitmapped and function-based indexes can also
eliminate full-table scans. In some cases, an unnecessary full-table scan can be forced to
use an index by adding an index hint to the SQL statement.
Cache small-table full-table scansIn cases where a full-table scan is the fastest access
method, the administrator should ensure that a dedicated data buffer is available for the
rows. In Oracle8 and beyond, a small table can be cached by forcing it into the KEEP
pool.
Verify optimal index usageOracle sometimes has a choice of indexes, and the tuning
professional must examine each index and ensure that Oracle is using the proper index.
Materialize your aggregations and summaries for static tables - One features of the
Oracle 10g SQLAccess advisor is recommendations for new indexes and suggestions for
materialized views. Materialized views pre-join tables and pre-summarize data, a real
silver bullet for data mart reporting databases where the data is only updated daily. Again,
see the book "Oracle Tuning: The Definitive Reference", for complete details on SQL
tuning with materialized views.
These are the goals of SQL tuning in a nutshell. However, they are deceptively simple, and to
effectively meet them, we need to have a through understanding of the internals of Oracle SQL.
Let's begin with an overview of the Oracle SQL optimizers.
Oracle SQL optimizers
One of the first things the Oracle DBA looks at is the default optimizer mode for the database.
The Oracle initialization parameters offer many cost-based optimizer modes as well as the
deprecated yet useful rule-based hint:
The cost-based optimizer uses statistics that are collected from the table using the analyze
table command. Oracle uses these metrics about the tables in order to intelligently determine the
most efficient way of servicing the SQL query. It is important to recognize that in many cases,
the cost-based optimizer may not make the proper decision in terms of the speed of the query.
The cost-based optimizer is constantly being improved, but there are still many cases in which
the rule-based optimizer will result in faster Oracle queries.
Prior to Oracle 10g, Oracle's default optimizer mode was called choose. In the choose
optimizer mode, Oracle will execute the rule-based optimizer if there are no statistics present for
the table; it will execute the cost-based optimizer if statistics are present. The danger with using
the choose optimizer mode is that problems can occur in cases where one Oracle table in a
complex query has statistics and the other tables do not.
Starting in Oracle 10g, the default optimizer mode is all_rows, favoring full-table scans over
index access. The all_rows optimizer mode is designed to minimize computing resources and it
favors full-table scans. Index access (first_rows_n) adds additional I/O overhead, but they return
rows faster, back to the originating query:
When only some tables contain CBO statistics, Oracle will use the cost-based optimization and
estimate statistics for the other tables in the query at runtime. This can cause significant
slowdown in the performance of the individual query.
In sum, the Oracle database administrator will always try changing the optimizer mode for
queries as the very first step in Oracle tuning. The foremost tenet of Oracle SQL tuning is
avoiding the dreaded full-table scan. One of the hallmarks of an inefficient SQL statement is the
failure of the SQL statement to use all of the indexes that are present within the Oracle database
in order to speed up the query.
Of course, there are times when a full-table scan is appropriate for a query, such as when you are
doing aggregate operations such as a sum or an average, and the majority of the rows within the
Oracle table must be read to get the query results. The task of the SQL tuning expert is to
evaluate each full-table scan and see if the performance can be improved by adding an index.
In most Oracle systems, a SQL statement will be retrieving only a small subset of the rows
within the table. The Oracle optimizers are programmed to check for indexes and to use them
whenever possible to avoid excessive I/O. However, if the formulation of a query is inefficient,
the cost-based optimizer becomes confused about the best access path to the data, and the costbased optimizer will sometimes choose to do a full-table scan against the table. Again, the
general rule is for the Oracle database administrator to interrogate the SQL and always look for
full-table scans.
For the full story, see my book "Oracle Tuning: The Definitive Reference" for details on
choosing the right optimizer mode.
A strategic plan for Oracle SQL tuning
Many people ask where they should start when tuning Oracle SQL. Tuning Oracle SQL is like
fishing. You must first fish in the Oracle library cache to extract SQL statements and rank the
statements by their amount of activity.
Step 1Identify high-impact SQL
The SQL statements will be ranked according the number of executions and will be tuned in this
order. The executions column of the v$sqlarea view and the stats$sql_summary or the
dba_hist_sql_summary table can be used to locate the most frequently used SQL. Note that we
can display SQL statements by:
Rows processedQueries that process a large number of rows will have high I/O and
may also have impact on the TEMP tablespace.
Disk readsHigh disk reads indicate a query that is causing excessive I/O.
CPU secsThis identifies the SQL statements that use the most processor resources.
SortsSorts can be a huge slowdown, especially if theyre being done on a disk in the
TEMP tablespace.
ExecutionsThe more frequently executed SQL statements should be tuned first, since
they will have the greatest impact on overall performance.
Most relational databases use an explain utility that takes the SQL statement as input, runs the
SQL optimizer, and outputs the access path information into a plan_table, which can then be
interrogated to see the access methods. Listing 1 runs a complex query against a database.
EXPLAIN PLAN SET STATEMENT_ID = 'test1' FOR
SET STATEMENT_ID = 'RUN1'
INTO plan_table
FOR
SELECT
'T'||plansnet.terr_code, 'P'||detplan.pac1
|| detplan.pac2 || detplan.pac3, 'P1', sum(plansnet.ytd_d_ly_tm),
sum(plansnet.ytd_d_ty_tm),
sum(plansnet.jan_d_ly),
sum(plansnet.jan_d_ty),
FROM plansnet, detplan
WHERE
plansnet.mgc = detplan.mktgpm
AND
detplan.pac1 in ('N33','192','195','201','BAI',
'P51','Q27','180','181','183','184','186','188',
'198','204','207','209','211')
GROUP BY 'T'||plansnet.terr_code, 'P'||detplan.pac1 || detplan.pac2 || detplan.pac3;
This syntax is piped into the SQL optimizer, which will analyze the query and store the plan
information in a row in the plan table identified by RUN1. Please note that the query will not
execute; it will only create the internal access information in the plan table. The plan tables
contains the following fields:
operationThe type of access being performed. Usually table access, table merge, sort,
or index operation
Parent_IDThe parent of the query component. Note that several query components
may have the same parent.
Now that the plan_table has been created and populated, you may interrogate it to see your
output by running the following query in Listing 2.
plan.sql - displays contents of the explain plan table
SET PAGES 9999;
SELECT lpad(' ',2*(level-1))||operation operation,
options,
object_name,
position
FROM plan_table
START WITH id=0
AND
statement_id = 'RUN1'
CONNECT BY prior id = parent_id
AND
statement_id = 'RUN1';
Listing 3 shows the output from the plan table shown in Listing 1. This is the execution plan for
the statement and shows the steps and the order in which they will be executed.
SQL> @list_explain_plan
OPERATION
------------------------------------------------------------------------------------OPTIONS
OBJECT_NAME
POSITION
------------------------------ ------------------------------------------------------SELECT STATEMENT
SORT
GROUP BY
1
CONCATENATION
1
NESTED LOOPS
1
TABLE ACCESS FULL
PLANSNET
1
TABLE ACCESS BY ROWID
DETPLAN
2
INDEX RANGE SCAN
DETPLAN_INDEX5
1
NESTED LOOPS
From this output, we can see the dreaded TABLE ACCESS FULL on the PLANSNET table. To
diagnose the reason for this full-table scan, we return to the SQL and look for any plansnet
columns in the WHERE clause. There, we see that the plansnet column called mgc is being
used as a join column in the query, indicating that an index is necessary on plansnet.mgc to
alleviate the full-table scan.
While the plan table is useful for determining the access path to the data, it does not tell the
entire story. The configuration of the data is also a consideration. The SQL optimizer is aware of
the number of rows in each table (the cardinality) and the presence of indexes on fields, but it is
not aware of data distribution factors such as the number of expected rows returned from each
query component.
Step 3Tune the SQL statement
For those SQL statements that possess a sub-optimal execution plan, the SQL will be tuned by
one of the following methods:
Rewriting the SQL in PL/SQL. For certain queries this can result in more than a 20x
performance improvement. The SQL would be replaced with a call to a PL/SQL package
that contained a stored procedure to perform the query.
Self-order the table joins - If you find that Oracle is joining the tables together in a suboptimal order, you can use the ORDERED hint to force the tables to be joined in the order that
they appear in the FROM clause. See
Try a first_rows_n hint. Oracle has two cost-based optimizer modes, first_rows_n and
all_rows. The first_rows mode will execute to begin returning rows as soon as possible, whereas
the all_rows mode is designed to optimize the resources on the entire query before returning
rows.
SELECT /*+ first_rows */
(SELECT count(*)
FROM REGISTRATION
WHERE
grade = 'A'
AND
student_id = STUDENT.student_id
);
Lets wind up with a review of the basic components of a SQL query and see how to optimize a
query for remote execution.
Tips for writing more efficient SQL
Space doesnt permit me to discuss every detail of Oracle tuning, but I can share some general
rules for writing efficient SQL in Oracle regardless of the optimizer that is chosen. These rules
may seem simplistic but following them in a diligent manner will generally relieve more than
half of the SQL tuning problems that are experienced:
Rewrite complex subqueries with temporary tables - Oracle created the global
temporary table (GTT) and the SQL WITH operator to help divide-and-conquer complex
SQL sub-queries (especially those with with WHERE clause subqueries, SELECT clause
scalar subqueries and FROM clause in-line views). Tuning SQL with temporary tables
(and materializations in the WITH clause) can result in amazing performance
improvements.
Use minus instead of EXISTS subqueries - Some say that using the minus operator
instead of NOT IN and NOT Exists will result in a faster execution plan.
Use SQL analytic functions - The Oracle analytic functions can do multiple
aggregations (e.g. rollup by cube) with a single pass through the tables, making them
very fast for reporting SQL.
Re-write NOT EXISTS and NOT EXISTS subqueries as outer joins - In many cases
of NOT queries (but ONLY where a column is defined as NULL), you can re-write the
uncorrelated subqueries into outer joins with IS NULL tests. Note that this is a noncorrelated sub-query, but it could be re-written as an outer join.
Below we combine the outer join with a NULL test in the WHERE clause without using a subquery, giving a faster execution plan.
select b.book_key from book b, sales s
where
b.book_key = s.book_key(+)
and
s.book_key IS NULL;
Index your NULL values - If you have SQL that frequently tests for NULL, consider
creating an index on NULL values. To get around the optimization of SQL queries that
choose NULL column values (i.e. where emp_name IS NULL), we can create a functionbased index using the null value built-in SQL function to index only on the NULL
columns.
Leave column names alone - Never do a calculation on an indexed column unless you
have a matching function-based index (a.k.a. FBI). Better yet, re-design the schema so
that common where clause predicates do not need transformation with a BIF:
where salary*5
> :myvalue
where substr(ssn,7,4)
= "1234"
where to_char(mydate,mon) = "january"
Avoid the use of NOT IN or HAVING. Instead, a NOT EXISTS subquery may run
faster (when appropriate).
Avoid the LIKE predicate = Always replace a "like" with an equality, when appropriate.
Never mix data types - If a WHERE clause column predicate is numeric, do not to use
quotes. For char index columns, always use quotes. There are mixed data type predicates:
Use decode and case - Performing complex aggregations with the decode or "case"
functions can minimize the number of times a table has to be selected.
Don't fear full-table scans - Not all OLTP queries are optimal when they uses indexes.
If your query will return a large percentage of the table rows, a full-table scan may be
faster than an index scan. This depends on many factors, including your configuration
(values for db_file_multiblock_read_count, db_block_size), query parallelism and the
number of table/index blocks in the buffer cache.
Use those aliases - Always use table aliases when referencing columns.
ORACLE HINTS
There are many Oracle hints available to the developer for use in tuning SQL statements that are
embedded in PL/SQL.
You should first get the explain plan of your SQL and determine what changes can be done to
make the code operate without using hints if possible. However, Oracle hints such as
ORDERED, LEADING, INDEX, FULL, and the various AJ and SJ Oracle hints can tame a wild
optimizer and give you optimal performance.
Oracle hints are enclosed within comments to the SQL commands DELETE, SELECT or
UPDATE or are designated by two dashes and a plus sign. To show the format the SELECT
statement only will be used, but the format is identical for all three commands.
SELECT
/*+ hint --or-- text */
statement body
-- or -SELECT
--+ hint --or-- text
statement body
Where:
-- - This is the comment delimiter for a single line comment (not usually
used for hints)
+ - This tells Oracle a hint follows, it must come immediately after the /*
Oracle Hint
Meaning
ALL_ROWS
CHOOSE
FIRST_ROWS
RULE
FULL(table)
HASH(table)
HASH_AJ(table)
ROWID(table)
INDEX(table [index])
INDEX_DESC(table [index])
INDEX_COMBINE(table index)
INDEX_FFS(table index)
MERGE_AJ (table)
AND_EQUAL(table index index This hint causes a merge on several single column indexes.
[index index index])
Two must be specified, five can be.
NL_AJ
HASH_SJ(t1, t2)
NL_SJ
STAR
STAR_TRANSFORMATION
FACT(table)
NO_FACT(table)
PUSH_SUBQ
REWRITE(mview)
NOREWRITE
Turns off query rewrite for the statement, use it for when data
returned must be concurrent and can't come from a
materialized view.
USE_CONCAT
NO_MERGE (table)
NO_EXPAND
USE_NL(table)
USE_MERGE(table,[table, - ])
DRIVING_SITE
LEADING(table)
The hint causes Oracle to use the specified table as the first
table in the join order.
NOPARALLEL (table
PARALLEL(table, instances)
PARALLEL_INDEX
Specifies that the blocks retrieved for the table in the hint are
placed at the most recently used end of the LRU list when the
table is full table scanned.
NOCACHE
Specifies that the blocks retrieved for the table in the hint are
placed at the least recently used end of the LRU list when the
table is full table scanned.
[NO]APPEND
For insert operations will append (or not append) data at the
HWM of table.
UNNEST
NO_UNNEST
PUSH_PRED
As you can see, a dilemma with a stubborn index can be easily solved using FULL or
NO_INDEX Oracle hints. You must know the application to be tuned. The DBA can provide
guidance to developers but in all but the smallest development projects, it will be nearly
impossible for a DBA to know everything about each application. It is clear that responsibility
for application tuning rests solely on the developer's shoulders with help and guidance from the
DBA.
While Oracle hints normally refer to table in the query it is possible to specify a hint for a table
within a view through the use of what are known as Oracle GLOBAL HINTS. This is done using
the Oracle global hint syntax. Any table hint can be transformed into an Oracle global hint.
The syntax is:
/*+ hint(view_name.table_in_view) */
For example:
/*+ full(sales_totals_vw.s_customer)*/
If the view is an inline view, place an alias on it and then use the alias to reference the inline
view in the Oracle global hint.
TKPROF
If a suitable plan table is not present one can be created by doing the fooling as the SYS user.
@ORACLE_HOMErdbmsadminutlxplan.sql
CREATE PUBLIC SYNONYM PLAN_TABLE FOR SYS.PLAN_TABLE;
GRANT SELECT, INSERT, UPDATE, DELETE ON SYS.PLAN_TABLE TO PUBLIC;
The resulting trace file will be located in the USER_DUMP_DEST directory. This can then be
interpreted using TKPROF at the commmand prompt as follows.
TKPROF <trace-file> <output-file> explain=user/password@service
table=sys.plan_table
The resulting output file contains the following type of information for all SQL statements
processed, including the ALTER SESSION commands.
******************************************************************************
**
count
= number of times OCI procedure was executed
cpu
= cpu time in seconds executing
elapsed = elapsed time in seconds executing
disk
= number of physical reads of buffers from disk
query
= number of buffers gotten for consistent read
current = number of buffers gotten in current mode (usually for update)
rows
= number of rows processed by the fetch or execute call
******************************************************************************
**
SELECT COUNT(*)
FROM
dual
call
count
------- ----Parse
1
Execute
1
Fetch
2
------- ----total
4
cpu elapsed
disk
query current
----- ------- ------- ------- ------0.02
0.02
0
0
0
0.00
0.00
0
0
0
0.00
0.00
0
1
4
----- ------- ------- ------- ------0.02
0.02
0
1
4
rows
------0
0
1
------1
When tracing lots of statements at once, such as batch processes, you can quickly discard
those statements which have an acceptable cpu times. It's often better to focus on those
statements that are taking most of the cpu time.
Inefficient statements are mostly associated with a high number of block visits. The query
column indicates block visits for read consistency, including all query and subquery
processing. The current column indicates visits not related to read consistency, including
segment headers and blocks that are going to be updated.
The number of blocks read from disk is shown in the disk column. Since disk reads are
slower than memory reads you would expect this value to be significantly lower than the
sum of the query and current columns. If it is not you may have an issue with your buffer
cache.
Locking problems and inefficient PL/SQL loops may lead to high cpu/elapsed values
even when block visits are low.
Multiple parse calls for a single statement imply a library cache issue.
Once you've identified your problem statements you can check the execution plan to see
why the statement is performing badly.