使用PL/SQL PROFILER 剖析PL/SQL代码是快速定位PL/SQL代码段最有效的方法。在上一篇文章使用PL/SQL PROFILER 定位 PL/SQL 瓶颈代码中描述了安装PROFILER,并给出了剖析的示例。本文参照了Tom大师的代码来对比剖析前后的性能并附上其代码。
1、用于实施剖析的存储过程
--环境
sys@USBO> select * from v$version where rownum<2;
BANNER
------------------------------------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
--用于实施剖析的原过程,下面是使用字面量的过程,注意代码中包含了启用与停止profiler
scott@USBO> create or replace procedure binds
2 is
3 v_num number;
4 begin
5 dbms_profiler.start_profiler('binds');
6 for i in 1..100000 loop
7 v_num := dbms_random.random;
8 insert into t1 values (v_num,v_num);
9 end loop;
10 dbms_profiler.stop_profiler;
11 end;
12 /
Procedure created.
--用于实施剖析的原过程,下面是使用绑定量的过程,注意代码中包含了启用与停止profiler
scott@USBO> create or replace procedure literals
2 is
3 v_num number;
4 begin
5 dbms_profiler.start_profiler('literals');
6 for i in 1..100000 loop
7 v_num := dbms_random.random;
8 execute immediate
9 'insert into t1 values ('||v_num||','||v_num||')';
10 end loop;
11 dbms_profiler.stop_profiler;
12 end;
13 /
Procedure created.
--Author : Leshami
--Blog : http://blog.csdn.net/leshami
--清除剖析表中的历史数据(每次剖析对比前执行)
scott@USBO> delete from plsql_profiler_data;
scott@USBO> delete from plsql_profiler_units;
scott@USBO> delete from plsql_profiler_runs;
--分别执行两个不同的过程
scott@USBO> exec literals;
scott@USBO> exec binds;
2、提取剖析对比结果
scott@USBO> @profsum
2 rows updated.
PL/SQL procedure successfully completed.
=
=
====================
total time
GRAND_TOTAL
-----------
58.93
=
=
====================
total time spent on each run
RUNID RUN_COMMENT SECS
------- ----------- ---------
7 literals 53.19
8 binds 5.75
=
=
====================
percentage of time in each module, for each run separately
RUNID RUN_COMMENT UNIT_OWNER UNIT_NAME SECS PERCEN
------- ----------- ----------- -------------- --------- ------
7 literals SCOTT LITERALS 53.19 100.0
8 binds SCOTT BINDS 5.75 100.0
=
=
====================
percentage of time in each module, summarized across runs
UNIT_OWNER UNIT_NAME SECS PERCENTAG
----------- -------------- --------- ---------
SCOTT LITERALS 53.19 90.25
SCOTT BINDS 5.75 9.75
=
=
====================
lines taking more than 1% of the total time, each run separate
RUNID HSECS PCT OWNER UNIT_NAME LINE# TEXT
------- --------- ------- ----------- -------------- ------ ---------------------
7 5221.18 88.6 SCOTT LITERALS 8 execute immediate
8 502.97 8.5 SCOTT BINDS 8 insert into t1 values
(v_num,v_num);
7 73.04 1.2 SCOTT LITERALS 7 v_num :=
dbms_random.random;
=
=
====================
most popular lines (more than 1%), summarize across all runs
HSECS PCT UNIT_OWNER UNIT_NAME LINE# TEXT
--------- ------- ----------- -------------- ------ ---------------------
5221.18 88.6 SCOTT LITERALS 8 execute immediate
502.97 8.5 SCOTT BINDS 8 insert into t1 values
(v_num,v_num);
73.04 1.2 SCOTT LITERALS 7 v_num :=
dbms_random.random;
PL/SQL procedure successfully completed.
=
=
====================
Number of lines actually executed in different units (by unit_name)
UNIT_OWNER UNIT_NAME LINES_EXECUTED LINES_PRESENT PCT
----------- -------------- -------------- ------------- -------
SCOTT LITERALS 4 7 57.1
SCOTT BINDS 4 7 57.1
=
=
====================
Number of lines actually executed for all units
LINES_EXECUTED
--------------
8
=
=
====================
total number of lines in all units
LINES_PRESENT
-------------
14
3、生成剖析结果的脚本
a、脚本profrep.sql
--在执行剖析前需要先执行该脚本以准备环境(仅首次使用)
Rem Copyright (c) Oracle Corporation 1998, 1999. All Rights Reserved.
Rem
Rem NAME
Rem profrep.sql
Rem
Rem DESCRIPTION
Rem PL/SQL Profiler reporting utilities
Rem
Rem NOTES
Rem The reporting procedures expect server output to be set on
Rem Some of the rollup functions commit the transaction.
Rem
-- First create the views used in the reporting package
--
create or replace view plsql_profiler_grand_total as
select sum(total_time) as grand_total from plsql_profiler_units;
create or replace view plsql_profiler_units_cross_run as
select unit_owner, unit_name, unit_type, sum(total_time) as total_time
from plsql_profiler_units group by unit_owner, unit_name, unit_type;
create or replace view plsql_profiler_lines_cross_run as
select p1.unit_owner as unit_owner, p1.unit_name as unit_name,
p1.unit_type as unit_type,
p2.line# as line#,
sum(p2.total_occur) as total_occur,
sum(p2.total_time) as total_time,
min(p2.min_time) as min_time,
max(p2.max_time) as max_time
from plsql_profiler_units p1, plsql_profiler_data p2
where p1.runid=p2.runid and p1.unit_number = p2.unit_number
group by p1.unit_owner, p1.unit_name, p1.unit_type, p2.line#;
create or replace view plsql_profiler_notexec_lines as
select owner, name, type, line, text, total_occur
from all_source t1, plsql_profiler_lines_cross_run t2
where t2.total_occur = 0 and t2.unit_owner = owner
and t2.unit_name = name and t2.unit_type = type and t2.line# = line
order by line asc;
create or replace package prof_report_utilities
authid current_user is
-- Routines to roll up profile information from line level to unit level
--
procedure rollup_unit(run_number IN number, unit IN number);
procedure rollup_run(run_number IN number);
procedure rollup_all_runs;
-- Routines to print a report, treating each run separately
--
procedure print_unit(run_number IN number, unit IN number);
procedure print_run(run_number IN number);
procedure print_detailed_report;
-- Routine to print a single report including information from each run
--
procedure print_summarized_report;
-- Set size of window for reports
procedure set_window_size(window_size IN pls_integer);
end prof_report_utilities;
/
show errors;
create or replace
package body prof_report_utilities is
-- the reports print 'window' lines of source around lines with profiler
-- data, otherwise skipping lines with no data. This is useful when
-- viewing data for units with sparse profiler data.
--
window pls_integer := 10;
last_line_printed number := 999999999;
cursor c2(run number, unit number,
owner_name varchar2, unit_name varchar2, unit_type varchar2) is
select line, text, total_occur, total_time, min_time, max_time
from all_source, plsql_profiler_data
where runid (+) = run and unit_number (+) = unit
and owner = owner_name and name = unit_name and type = unit_type
and plsql_profiler_data.line# (+) = line
order by line asc;
-- c2tab contains the window of lines around any line with interesting
-- data.
type c2tab_t is table of c2%rowtype index by binary_integer;
c2tab c2tab_t;
-- index into the window where previous row was inserted
prev_row pls_integer := 0;
procedure report_exception(which IN varchar2, reraised IN boolean) is
begin
dbms_output.new_line();
dbms_output.new_line();
dbms_output.put('======================================');
dbms_output.put_line('======================================');
dbms_output.put('Exception Number: ');
dbms_output.put(sqlcode);
dbms_output.put(' raised in routine ' || which );
if (reraised)
then
dbms_output.put(' (Will be reraised)');
end if;
dbms_output.new_line();
dbms_output.put('======================================');
dbms_output.put_line('======================================');
end;
-- compute the total time spent executing this unit - the sum of the
-- time spent executing lines in this unit (for this run)
--
procedure rollup_unit(run_number IN number, unit IN number) is
begin
dbms_profiler.rollup_unit(run_number, unit);
exception
when others then
report_exception('Rollup_Unit', true);
raise;
end rollup_unit;
-- rollup all units for the given run
--
procedure rollup_run(run_number IN number) is
begin
dbms_profiler.rollup_run(run_number);
exception
when others then
report_exception('Rollup_Run', true);
raise;
end rollup_run;
procedure rollup_all_runs is
cursor crunid is
select runid from plsql_profiler_runs order by runid asc;
begin
for runidrec in crunid loop
dbms_profiler.rollup_run(runidrec.runid);
end loop crunid;
end rollup_all_runs;
--
-- Reporting functions
--
-- Format and print information on a unit
--
procedure print_unit_header(run_number IN number, unit IN number) is
cursor cuhdr(run_number number, unit number) is
select * from plsql_profiler_units
where runid = run_number and unit_number = unit;
unit_row cuhdr%rowtype;
begin
-- fetch data for the given unit
open cuhdr(run_number, unit);
fetch cuhdr into unit_row;
close cuhdr;
-- format and print the data
dbms_output.put('Unit #');
dbms_output.put(unit_row.unit_number);
dbms_output.put(': ');
dbms_output.put(unit_row.unit_owner || '.' || unit_row.unit_name);
dbms_output.put(' - Total time: ');
dbms_output.put(to_char(unit_row.total_time/1000000000, '99999.99'));
dbms_output.put_line(' seconds');
end print_unit_header;
-- Format and print information on a run
--
procedure print_run_header(run_number IN number) is
cursor crun(run_number number) is
select * from plsql_profiler_runs where runid = run_number;
runidrec crun%rowtype;
begin
open crun(run_number);
fetch crun into runidrec;
close crun;
dbms_output.new_line();
dbms_output.new_line();
dbms_output.put('===========================');
dbms_output.put('Results for run #');
dbms_output.put(runidrec.runid);
dbms_output.put(' made on ');
dbms_output.put(to_char(runidrec.run_date, 'DD-MON-YY HH24:MI:SS'));
dbms_output.put_line(' =========================');
if (runidrec.run_comment is not null) then
dbms_output.put(' (');
dbms_output.put(runidrec.run_comment);
dbms_output.put(') ');
end if;
dbms_output.put('Run total time: ');
dbms_output.put(to_char(runidrec.run_total_time/1000000000, '99999.99'));
dbms_output.put_line(' seconds');
if (runidrec.run_system_info is not null) then
dbms_output.put_line(runidrec.run_system_info);
end if;
end print_run_header;
--
-- Routines for formatting and printing profiler data
--
-- Format and print one line of data and source
--
procedure print_line(line number, lcount number,
running_total number, source varchar2) is
outline varchar2(200); -- temp buffer to hold output
cline varchar2(40); -- number of times this line was executed
total_time varchar2(40); -- total time executing this line
ave_time varchar2(40); -- average time for this line
ave_nano number;
begin
outline := to_char(line, '99G999');
-- format and store away the count and running total
if (lcount is not null) then
cline := to_char(lcount, '99G999G999');
end if;
if (running_total is not null) then
total_time := substr(to_char(running_total/1000000000), 1, 9);
end if;
-- compute average time executing this line and stash it away
if (lcount > 0) then
ave_nano := running_total/lcount;
ave_time := substr(to_char(ave_nano/1000000000), 1, 9);
end if;
-- now put together all the data, the source line and output it
--
outline := outline || ' ' || cline || ' ' ||
total_time || ' ' || ave_time || ' ';
if source is not null then
outline := rpad(outline, 55) || substr(source, 1, (length(source) - 1));
end if;
dbms_output.put_line(outline);
end print_line;
-- insert a c2 row into the window
--
procedure insert_into_window(c2row c2%rowtype) is
next_row pls_integer;
begin
next_row := mod((prev_row + 1), window);
c2tab(next_row) := c2row;
prev_row := next_row;
end insert_into_window;
-- clear out the window (for reuse later)
--
procedure clear_window is
empty_tab c2tab_t;
begin
-- throw away table
c2tab := empty_tab;
prev_row := 0;
end clear_window;
-- print the window and throw it away
--
procedure print_window(start_separator IN boolean) is
next_row pls_integer;
iter pls_integer;
c2row c2%rowtype;
first_line boolean := true;
ct number := c2tab.count;
begin
if (window <= 0) then
return;
end if;
-- compute first row
next_row := mod((prev_row + 1), window);
-- Detect the case where the window hasn't wrapped around yet
if (not c2tab.exists(next_row)) then
next_row := c2tab.next(next_row);
if (next_row is NULL) then
next_row := c2tab.first;
end if;
end if;
for iter in 1..window loop
exit when (ct <= 0);
if (c2tab.exists(next_row)) then
c2row := c2tab(next_row);
if (first_line and (last_line_printed < c2row.line-1)) then
dbms_output.put_line('.');
dbms_output.put_line('.');
dbms_output.put_line('.');
end if;
first_line := false;
print_line(c2row.line, c2row.total_occur,
c2row.total_time, c2row.text);
last_line_printed := c2row.line;
ct := ct - 1;
end if;
next_row := mod((next_row + 1), window);
end loop;
if (not start_separator) then
last_line_printed := 999999999;
end if;
clear_window;
end print_window;
procedure print_unit(run_number number, unit number) is
cursor cuhdr(run number, unit number) is
select * from plsql_profiler_units
where runid = run and unit_number = unit;
unit_row cuhdr%rowtype;
joined_row c2%rowtype;
lcount number;
-- print a trailing window after the last interesting line
print_trailing_window boolean := false;
trail_count pls_integer := 0;
begin
dbms_profiler.rollup_unit(run_number, unit);
-- fetch unit name and type information
open cuhdr(run_number, unit);
fetch cuhdr into unit_row;
close cuhdr;
-- If there was an error previously, cursor "c2" might be open
-- Close it, and ignore the error if it already was
--
begin
close c2;
exception
when others then
null;
end;
open c2(run_number, unit,
unit_row.unit_owner, unit_row.unit_name, unit_row.unit_type);
loop
fetch c2 into joined_row;
exit when c2%notfound;
lcount := joined_row.total_occur;
-- if there is interesting data at this line, print its prefix window
-- and the data itself; else stash away this line c2tab - it may get
-- printed as part of another line's window
if (lcount is not null and lcount <> 0) then
print_window (start_separator => false);
print_line(joined_row.line, joined_row.total_occur,
joined_row.total_time, joined_row.text);
print_trailing_window := true;
trail_count := 0;
else
insert_into_window(joined_row);
-- if we are now accumulating rows after a row with data, increment
-- count of rows accumulated since last interesting row. if we have
-- accumulated a window full of data, print it out.
if (print_trailing_window) then
trail_count := trail_count + 1;
if (trail_count = window) then
print_window(start_separator => true);
print_trailing_window := false;
end if;
end if;
end if;
end loop;
close c2;
-- if the window isn't empty, print it out.
if (print_trailing_window) then
print_window(start_separator => false);
print_trailing_window := false;
end if;
clear_window;
exception
when others then
report_exception('Print_Unit', false);
end print_unit;
procedure print_run(run_number number) is
cursor cunits(run_number number) is
select unit_number from plsql_profiler_units
where runid = run_number order by unit_number asc;
begin
print_run_header(run_number);
dbms_profiler.rollup_run(run_number);
for unitrec in cunits(run_number) loop
print_unit_header(run_number, unitrec.unit_number);
print_unit(run_number, unitrec.unit_number);
end loop;
exception
when others then
report_exception('Print_Run', false);
end print_run;
procedure print_detailed_report is
cursor crunid is
select runid from plsql_profiler_runs order by runid asc;
begin
dbms_output.enable(999999);
dbms_output.put('=================================');
dbms_output.put('trace info');
dbms_output.put_line('=================================');
rollup_all_runs();
for runidrec in crunid loop
print_run(runidrec.runid);
end loop crunid;
dbms_output.new_line;
dbms_output.put('======================================');
dbms_output.put_line('======================================');
exception
when others then
report_exception('Print_Detailed_Report', false);
end print_detailed_report;
procedure print_summarized_unit(owner_name varchar2,
unit_name varchar2, unit_type varchar2) is
cursor c3(uowner varchar2, uname varchar2, utype varchar2) is
select line, text, total_occur, total_time, min_time, max_time
from all_source t1, plsql_profiler_lines_cross_run t2
where owner = uowner and name = uname and type = utype
and t2.unit_owner (+) = uowner and t2.unit_name (+) = uname
and t2.unit_type (+) = utype and t2.line# (+) = line
order by line asc;
datarec c3%rowtype;
lcount number;
-- print a trailing window after the last interesting line
print_trailing_window boolean := false;
trail_count pls_integer := 0;
begin
open c3(owner_name, unit_name, unit_type);
loop
fetch c3 into datarec;
exit when c3%notfound;
lcount := datarec.total_occur;
if (lcount is not null and lcount <> 0) then
print_window (start_separator => false);
print_line(datarec.line,
datarec.total_occur,
datarec.total_time,
datarec.text);
print_trailing_window := true;
trail_count := 0;
else
insert_into_window(datarec);
-- if we are now accumulating rows after a row with data, increment
-- count of rows accumulated since last interesting row. if we have
-- accumulated a window full of data, print it out.
if (print_trailing_window) then
trail_count := trail_count + 1;
if (trail_count = window) then
print_window(start_separator => true);
print_trailing_window := false;
end if;
end if;
end if;
end loop;
close c3;
-- if the window isn't empty, print it out.
if (print_trailing_window) then
print_window(start_separator => false);
print_trailing_window := false;
end if;
clear_window;
exception
when others then
report_exception('Print_Summarized_Unit', false);
end print_summarized_unit;
procedure print_summarized_report is
cursor cunits is
select unit_owner, unit_name, unit_type
from plsql_profiler_units_cross_run
order by unit_owner, unit_name asc;
begin
rollup_all_runs();
dbms_output.enable(9999999);
dbms_output.put('================== Profiler report - all runs rolled up');
dbms_output.put_line(' ===================');
for unitrec in cunits loop
dbms_output.put('Unit ');
dbms_output.put(unitrec.unit_owner);
dbms_output.put('.');
dbms_output.put(unitrec.unit_name);
dbms_output.put_line(':');
print_summarized_unit(unitrec.unit_owner,
unitrec.unit_name, unitrec.unit_type);
end loop;
dbms_output.new_line;
dbms_output.put('======================================');
dbms_output.put_line('======================================');
exception
when others then
report_exception('Print_Summarized_Report', false);
end print_summarized_report;
-- Set size of window for reports
procedure set_window_size(window_size IN pls_integer) is
begin
if (window_size < 0) then
window := 999999999;
else
window := window_size;
end if;
end set_window_size;
end prof_report_utilities;
/
show errors;
b、剖析报告脚本profsum.sql
--file_name: profsum.sql
set echo off
set linesize 5000
set trimspool on
set serveroutput on
set termout off
column owner format a11
column unit_name format a14
column text format a21 word_wrapped
column runid format 999999
column secs format 99999.99
column hsecs format 99999.99
column grand_total format 9999.99
column run_comment format a11 word_wrapped
column line# format 99999
column pct format 9999.9
column unit_owner format a11
spool profsum.out
--Clean out rollup results, and recreate --
update plsql_profiler_units set total_time = 0;
execute prof_report_utilities.rollup_all_runs;
prompt =
prompt =
prompt ====================
prompt total time
select grand_total/1000000000 as grand_total
from plsql_profiler_grand_total;
prompt =
prompt =
prompt ====================
prompt total time spent on each run
select runid,
substr(run_comment,1, 30) as run_comment,
run_total_time/1000000000 as secs
from (select a.runid, sum(a.total_time) run_total_time, b.run_comment
from plsql_profiler_units a, plsql_profiler_runs b
where a.runid = b.runid group by a.runid, b.run_comment )
where run_total_time > 0
order by runid asc;
prompt =
prompt =
prompt ====================
prompt percentage of time in each module, for each run separately
select p1.runid,
substr(p2.run_comment, 1, 20) as run_comment,
p1.unit_owner,
decode(p1.unit_name, '', '',
substr(p1.unit_name,1, 20)) as unit_name,
p1.total_time/1000000000 as secs,
TO_CHAR(100*p1.total_time/p2.run_total_time, '999.9') as percentage
from plsql_profiler_units p1,
(select a.runid, sum(a.total_time) run_total_time, b.run_comment
from plsql_profiler_units a, plsql_profiler_runs b
where a.runid = b.runid group by a.runid, b.run_comment ) p2
where p1.runid=p2.runid
and p1.total_time > 0
and p2.run_total_time > 0
and (p1.total_time/p2.run_total_time) >= .01
order by p1.runid asc, p1.total_time desc;
column secs form 99999.99
prompt =
prompt =
prompt ====================
prompt percentage of time in each module, summarized across runs
select p1.unit_owner,
decode(p1.unit_name, '', '', substr(p1.unit_name,1, 25)) as
unit_name,
p1.total_time/1000000000 as secs,
TO_CHAR(100*p1.total_time/p2.grand_total, '99999.99') as percentage
from plsql_profiler_units_cross_run p1,
plsql_profiler_grand_total p2
order by p1.total_time DESC;
prompt =
prompt =
prompt ====================
prompt lines taking more than 1% of the total time, each run separate
select p1.runid as runid,
p1.total_time/10000000 as hsecs,
p1.total_time/p4.grand_total*100 as pct,
substr(p2.unit_owner, 1, 20) as owner,
decode(p2.unit_name, '', '', substr(p2.unit_name,1, 20)) as
unit_name,
p1.line#,
( select p3.text
from all_source p3
where p3.owner = p2.unit_owner and
p3.line = p1.line# and
p3.name=p2.unit_name and
p3.type not in ( 'PACKAGE', 'TYPE' )) text
from plsql_profiler_data p1,
plsql_profiler_units p2,
plsql_profiler_grand_total p4
where (p1.total_time >= p4.grand_total/100)
AND p1.runid = p2.runid
and p2.unit_number=p1.unit_number
order by p1.total_time desc;
prompt =
prompt =
prompt ====================
prompt most popular lines (more than 1%), summarize across all runs
select p1.total_time/10000000 as hsecs,
p1.total_time/p4.grand_total*100 as pct,
substr(p1.unit_owner, 1, 20) as unit_owner,
decode(p1.unit_name, '', '',
substr(p1.unit_name,1, 20)) as unit_name,
p1.line#,
( select p3.text from all_source p3
where (p3.line = p1.line#) and
(p3.owner = p1.unit_owner) AND
(p3.name = p1.unit_name) and
(p3.type not in ( 'PACKAGE', 'TYPE' ) ) ) text
from plsql_profiler_lines_cross_run p1,
plsql_profiler_grand_total p4
where (p1.total_time >= p4.grand_total/100)
order by p1.total_time desc;
execute prof_report_utilities.rollup_all_runs;
prompt =
prompt =
prompt ====================
prompt Number of lines actually executed in different units (by unit_name)
select p1.unit_owner,
p1.unit_name,
count( decode( p1.total_occur, 0, null, 0)) as lines_executed ,
count(p1.line#) as lines_present,
count( decode( p1.total_occur, 0, null, 0))/count(p1.line#) *100
as pct
from plsql_profiler_lines_cross_run p1
where (p1.unit_type in ( 'PACKAGE BODY', 'TYPE BODY',
'PROCEDURE', 'FUNCTION' ) )
group by p1.unit_owner, p1.unit_name;
prompt =
prompt =
prompt ====================
prompt Number of lines actually executed for all units
select count(p1.line#) as lines_executed
from plsql_profiler_lines_cross_run p1
where (p1.unit_type in ( 'PACKAGE BODY', 'TYPE BODY',
'PROCEDURE', 'FUNCTION' ) )
AND p1.total_occur > 0;
prompt =
prompt =
prompt ====================
prompt total number of lines in all units
select count(p1.line#) as lines_present
from plsql_profiler_lines_cross_run p1
where (p1.unit_type in ( 'PACKAGE BODY', 'TYPE BODY',
'PROCEDURE', 'FUNCTION' ) );
spool off
set termout on
edit profsum.out
set linesize 131
更多参考
DML Error Logging 特性
PL/SQL --> 游标
PL/SQL --> 隐式游标(SQL%FOUND)
批量SQL之 FORALL 语句
批量SQL之 BULK COLLECT 子句
PL/SQL 集合的初始化与赋值
PL/SQL 联合数组与嵌套表
PL/SQL 变长数组
PL/SQL --> PL/SQL记录
SQL tuning 步骤
高效SQL语句必杀技
父游标、子游标及共享游标
绑定变量及其优缺点
dbms_xplan之display_cursor函数的使用
dbms_xplan之display函数的使用
执行计划中各字段各模块描述
使用 EXPLAIN PLAN 获取SQL语句执行计划