by
Alex Bubernak
Oracle is always improving life for DBAs and developers
with each release of the RDBMS. 10g has many new features to help troubleshoot
and tune the database. Starting with 10g, Oracle has introduced Advisors. These
Advisors are part of the ADDM (Automatic Database Diagnostic Monitor) and run
in the background as long as the parameter STATICS_LEVEL is set to at least
TYPICAL, which is the default for a new database installed. Here we will
discuss some of the more common utilities used and how they can help.
DBMS_SQLTUNE package
This package is of great help in tuning SQL statements. Simply
put, it analyzes the SQL statement and gives back recommendations on how to improve
the statements performance if it finds a better way. It will show you the
current execution plan as well as the execution plan if you were to make the
recommended changes. It will give the reasoning for the recommendation and even
give you the commands to implement the recommended change. This is a great tool
to use in developing new applications as well as troubleshooting existing ones
in a production environment.
This package will let you create SQL profiles. SQL
profiling analyzes a statement and offers a better Execution Plan if one is
available. You can apply this plan to the SQL profile and SQL will use this
plan the next time it is run. This can give better performance without changing
application code.
You can use this to compare SQL statements using tuning
sets.
Lets look at some of the components that make up the
DBMS_SQLTUNE package. Please see Oracle documentation for all components in the
DBMS_SQLTUNE package. I will show and explain the most commonly used procedures.
- CREATE_TUNING_TASK This function will create a new
tuning task and return the name of the task. A generated name will be given if
one is not specified. Some of the inputs are:
- SQL_ID This is the SQL_ID of an
existing SQL statement in the SGA. This id can be found in V$SQL, V$SQLTEXT,
V$SESSION and a few other V$ views. You cannot use this along with SQL_TEXT.
- SQL_TEXT This can be used to
enter the SQL statement manually if it is not in the SGA. You cannot use this
along with SQL_ID.
- BIND_LIST Pass in any bind
variables for SQL statement. Type of input is SQL_BINDS.
- USER_NAME The owner of the SQL
statement.
- SCOPE This sets the scope and
there are two different settings.
- LIMITED
This makes recommendations based on analysis of the following: SQL structure,
statistics and access path.
- COMPREHINSIVE
This makes recommendations based on all analysis of LIMITED and SQL
Profiling. Note: This is the most resource intensive and can take much time to
complete. A TIME_LIMIT setting can be specified to limit how long it should
analyze.
- TIME_LIMIT Time in seconds of how
long a COMPREHENSIZE analysis can run. The default is 30 minutes if not set.
That does not mean the analysis will run that long, just that is the limit of
how long it can run. If the analysis hasnt completed by the time limit, it
will give recommendations on what it has found so far.
- TASK_NAME Name given to identify
a task.
- DESCRIPTION To give a description
of the task.
- DROP_TUNING_TASK This procedure drops a tuning task
that has been created. It only takes one input parameter.
- TASK_NAME This is the name of the
task created with the CREATE_TUNING_TASK function.
- EXECUTE_TUNING_TASK Will run the created tuning
task.
- TASK_NAME Name of the task
created from CREATE_TUNING_TASK function.
- CANCEL_TUNING_TASK Cancel a task that is currently
running.
- TASK_NAME Name of the task
running from EXECUTE_TUNING_TASK.
- INTERRUPT_TUNING_TASK This will interrupt an
executing task and allow you to query data collected up to the interruption.
- TASK_NAME Name of the task
running from EXECUTE_TUNING_TASK.
- RESET_TUNING_TASK Reset a tuning task to its
initial state and delete all data collected.
- TASK_NAME Name of the task
created from CREATE_TUNING_TASK function.
- RESUME_TUNING_TASK Resume an interrupted tuning
task. This can only be used with tuning sets.
- TASK_NAME Name of the task
created from CREATE_TUNING_TASK function.
- REPORT_TUNING_TASK This will return a report of
what it found and offer any recommendations from the analysis.
- TASK_NAME Name of the task
executed from EXECUTE_TUNING_TASK procedure.
- TYPE Type of report to produce,
values are: HTML, XML and TEXT. Default is TEXT.
- LEVEL Level of detail for the
report, values are:
- BASIC Gives General
information section, Findings and Explain Plans.
- TYPICAL Same as BASIC except with
SQL profiling information, recommendations and more detailed Explain Plans.
This is the default.
- ALL Same as TYPICAL except with
detailed object information and very detailed Explain Plan.
- SECTION What section to show in the report; values
are:
- FINDINGS Report only the Finding
section.
- PLANS Report only the Explain
Plan section for current SQL and any recommendations.
- INFORMATION Report only the
General Information section.
- ERRORS Only report Error section
if found, otherwise only General Information shown.
- ALL Report every section. This
is the default.
- SCRIPT_TUNING_TASK This function will output PL/SQL
commands to implement recommendations from an executed tuning task.
- TASK_NAME Name of the task
created from CREATE_TUNING_TASK function.
- REC_TYPE Types of recommendations
to include. These can be separated by commas (e.g. MyTaskName,INDEXES,STATISTICS),
values are:
- PROFILES Only
commands to implement recommended SQL Profiles.
- STATISTICS Only
commands for stale and/or missing statistics.
- INDEXES Only commands
for index recommendations.
- ALL Commands for all
recommendations; this is the default.
There are more procedures in the DBMS_SQLTUNE package that I
will talk about later in this chapter. I want to show you how to use this tool
for tuning before going into detail about the other procedures. Now, enough of
the boring descriptions and lets start using this tool.
Lets create a small sample table with data.
Create_test_table.sql
drop table tb
/
create table tb (tb_seq number,
var_col1 varchar2(32),
var_col2 varchar2(64),
date_col date)
/
drop sequence tb_num_seq
/
create sequence tb_num_seq start with 1
/
declare
cnt number := 0;
begin
for cnt in 1 .. 100
loop
insert into tb values (tb_num_seq.nextval,
'Test',
'Description for test',
sysdate);
end loop;
end;
/
commit
/
In this example, we will tune a single SQL statement.
First, we create a new Tuning Task named Useless Task using
the CREATE_TUNING_TASK function.
DECLARE
my_task_name varchar2(30);
sql_txt clob;
BEGIN
sql_txt := 'select var_col2 from tb where TB_seq = :b1 and var_col2 = :b2';
my_task_name := DBMS_SQLTUNE.CREATE_TUNING_TASK(
sql_text => sql_txt,
-- user_name => 'MYUSER', -- if sql owner is different than current session
scope => 'COMPREHENSIVE',
time_limit => 300, -- 5 minute time limit
task_name => 'Useless_Task',
description => 'Tune Useless Query Task');
END;
/
Once run, the Useless Task will be created. We must now
execute the newly created task.
SQL> execute dbms_sqltune.execute_tuning_task (task_name => 'Useless_Task);
Note: If this task is running too long for your liking, you
can execute dbms_sqltune.interrupt_tuning_task(Useless Task); from another
session.
When the procedure completes, you can execute the following
SQL to return a report.
SQL> set long 5000 -- must set this to display output
SQL> set pagesize 0
SQL> select dbms_sqltune.report_tuning_task('Useless_Task') from dual;
Below is the output of the report. I numbered the lines in
the output for readability.
1 GENERAL INFORMATION SECTION
2 -------------------------------------------------------------------------------
3 Tuning Task Name : Useless_Task
4 Tuning Task Owner : MYUSER
5 Scope : COMPREHENSIVE
6 Time Limit(seconds) : 300
7 Completion Status : COMPLETED
8 Started at : 01/26/2007 14:25:37
9 Completed at : 01/26/2007 14:25:38
10 Number of Statistic Findings : 1
11 Number of Index Findings : 1
12
13 -------------------------------------------------------------------------------
14 Schema Name: MYUSER
15 SQL ID : guu7ppk7pu1a5
16 SQL Text : select var_col2 from tb where TB_seq = :b1 and var_col2 = :b2
17
18 -------------------------------------------------------------------------------
19 FINDINGS SECTION (2 findings)
20 -------------------------------------------------------------------------------
21
22 1- Statistics Finding
23 ---------------------
24 Table "MYUSER"."TB" was not analyzed.
25
26 Recommendation
27 --------------
28 - Consider collecting optimizer statistics for this table.
29 execute dbms_stats.gather_table_stats(ownname => 'MYUSER', tabname =>
30 'TB', estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
31 method_opt => 'FOR ALL COLUMNS SIZE AUTO');
32
33 Rationale
34 ---------
35 The optimizer requires up-to-date statistics for the table in order to
36 select a good execution plan.
37
38 2- Index Finding (see explain plans section below)
39 --------------------------------------------------
40 The execution plan of this statement can be improved by creating one or more
41 indices.
42
43 Recommendation (estimated benefit: 100%)
44 ----------------------------------------
45 - Consider running the Access Advisor to improve the physical schema design
46 or creating the recommended index.
47 create index MYUSER.IDX$$_51CC0001 on MYUSER.TB('TB_SEQ','VAR_COL2');
48
49 Rationale
50 ---------
51 Creating the recommended indices significantly improves the execution plan
52 of this statement. However, it might be preferable to run "Access Advisor"
53 using a representative SQL workload as opposed to a single statement. This
54 will allow to get comprehensive index recommendations which takes into
55 account index maintenance overhead and additional space consumption.
56
57 -------------------------------------------------------------------------------
58 EXPLAIN PLANS SECTION
59 -------------------------------------------------------------------------------
60
61 1- Original
62 -----------
63 Plan hash value: 1750851749
64
65 --------------------------------------------------------------------------
66 | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
67 --------------------------------------------------------------------------
68 | 0 | SELECT STATEMENT | | 1 | 47 | 3 (0)| 00:00:01 |
69 |* 1 | TABLE ACCESS FULL| TB | 1 | 47 | 3 (0)| 00:00:01 |
70 --------------------------------------------------------------------------
71
72 Predicate Information (identified by operation id):
73 ---------------------------------------------------
74
75 1 - filter("TB_SEQ"=:B1 AND "VAR_COL2"=:B2)
76
77 2- Using New Indices
78 --------------------
79 Plan hash value: 3914465704
80
81 --------------------------------------------------------------------------------
82 ---
83 | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
84 |
85 --------------------------------------------------------------------------------
86 ---
87 | 0 | SELECT STATEMENT | | 1 | 47 | 1 (0)| 00:00:0
88 1 |
89 |* 1 | INDEX RANGE SCAN| IDX$$_51CC0001 | 1 | 47 | 1 (0)| 00:00:0
90 1 |
91 --------------------------------------------------------------------------------
92 ---
93
94 Predicate Information (identified by operation id):
95 ---------------------------------------------------
96
97 1 - access("TB_SEQ"=:B1 AND "VAR_COL2"=:B2)
98
99 -------------------------------------------------------------------------------
Lines 1 through 16 are the General Information section. This
will show a summery of analysis.
Line 7 will give the status of the analysis.
7 Completion Status : COMPLETED
In this case the analysis completed; it would show
INTERRUPTED if you executed INTERRUPT_TUNING_TASK during execution of analysis.
The following shows under the General Information section
if the task ends before the analysis completed due to its running longer than the
TIME_LIMIT set in CREATE_TUNING_TASK. A similar error would show for
INTERRUPTED or if the execution of the task was cancelled using
CANCEL_TUNING_TASK..
ERRORS SECTION
-------------------------------------------------------------------------------
- The current operation was interrupted because it timed out.
-------------------------------------------------------------------------------
Line 15 is the sql id that can be linked to some of the V$
views.
15 SQL ID : guu7ppk7pu1a5
Lines 19 through 55 are the Findings section. This will tell
what it has found during the analysis. We can see that there are two findings.
Line 43 tells us potentially how much can be gained by implementing
the recommendation.
Lines 29 and 31 give the SQL to implement the first set of
recommendations.
29 execute dbms_stats.gather_table_stats(ownname => 'MYUSER', tabname =>
30 'TB', estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
31 method_opt => 'FOR ALL COLUMNS SIZE AUTO');
Line 47 gives the SQL to implement the second set of
recommendations.
Lines 61 on show the Explain Plan of the original statement.
And here is the great part, lines 77 on shows what the new plan will look like
if you implement the recommendations.
You can see the original plan shows a cost of 3 and the new
one is 1. An index is obvious in this case to improve this statement, but it
may be to your advantage to implement one recommendation at a time and run the
analysis again because the analysis may have better recommendations after you
implement the first. In this case, the analysis pointed to the table not having
any statistics. This can affect the index it recommends.
Lets implement the first recommendation.
Note: Always check the SQL before implementing, never
assume it to always be correct. Besides, you may want to name the objects
differently and maybe place them in a different tablespace. As in this example
we have to remove the single quotes from on MYUSER.TB('TB_SEQ'); in order to
create the correct index.
SQL> execute dbms_stats.gather_table_stats(ownname => 'MYUSER', tabname =>
'TB', estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
method_opt => 'FOR ALL COLUMNS SIZE AUTO');
Now if we execute, then run the report again, we will see
the index recommendation is different. That is because it now knows what the
data looks like.
create index MYUSER.IDX$$_51EA0001 on MYUSER.TB('TB_SEQ');
Now that youve seen a basic example, lets look at how to
create another tuning task by modifying the previous one. We can use the same task
name if we drop it first or use a different name; this way we can compare
different tasks.
This example is using bind variables.
tuning2.sql
1 DECLARE
2 my_task_name varchar2(30);
3 sql_txt clob;
4
5 BEGIN
6
7 sql_txt := 'select var_col2 from tb where TB_seq = :b1 and var_col2 = :b2';
8
9 my_task_name := DBMS_SQLTUNE.CREATE_TUNING_TASK(
10 sql_text => sql_txt,
11 bind_list => sql_binds(anydata.ConvertNumber(91),anydata.ConvertVarchar2('Test')),
12 -- user_name => 'MYUSER', -- if sql owner is different than current session
13 scope => 'COMPREHENSIVE',
14 time_limit => 300,
15 task_name => 'Useless_Task_WBinds',
16 description => 'Tune Useless Query Task Using Binds');
17 END;
18 /
Line 11 is where bind variables are entered using bind_list.
In the above we are entering two bind variables, anydata.ConvertNumber(91)
is entering the number 91 and anydata.ConvertVarchar2(Test) is a varchar
value of Test. Im not going to go into the details of SQL_BINDS, you can
find more information in Oracle documentation, but below is a list of input
functions.
ConvertNumber(IN NUMBER) RETURN AnyData
ConvertDate(IN DATE) RETURN AnyData
ConvertChar(IN CHAR) RETURN AnyData
ConvertVarchar(IN VARCHAR) RETURN AnyData
ConvertVarchar2(IN VARCHAR2) RETURN AnyData
ConvertRaw(IN RAW) RETURN AnyData
ConvertBlob(IN BLOB) RETURN AnyData
ConvertClob(IN CLOB) RETURN AnyData
ConvertBfile(IN BFILE) RETURN AnyData
ConvertObject(IN "<object_type>") RETURN AnyData
ConvertRef(IN REF "<object_type>") RETURN AnyData
ConvertCollection(IN "<COLLECTION_1>") RETURN AnyData
Once, the above is created and executed (remember that this
task will have to be dropped or a new name given before this task is created),
the report will show a new recommendation.
Recommendation (estimated benefit: 100%)
----------------------------------------
- Consider running the Access Advisor to improve the physical schema design
or creating the recommended index.
create index MYUSER.IDX$$_52AD0001 on MYUSER.TB('VAR_COL2');
Now lets change the bind data for the var_col2 column to a
value that is in the table. So anydata.ConvertVarchar2(Test) is changed to
anydata.ConvertVarchar2(Description for test). Lets recreate the task,
execute and run the report.
-------------------------------------------------------------------------------
There are no recommendations to improve the statement.
-------------------------------------------------------------------------------
Now we see that there arent any recommendations. From this example,
you can see how bind data can make a difference to the findings.