|
|
|||||||||||||
|
|
Now that the framework is in place, we’re ready to give it a spin. Let’s use a BULK version row-by-row block of code for testing, and this comes from Example 11-3 in the 10g PL/SQL user’s guide. Instead of 500 (too small of a value, may get 0 seconds returned for the bulk step), we’ll use 500,000 iterations. On a warmed-up system, the times are around 25 seconds versus 4 seconds.
CREATE TABLE parts1 (pnum INTEGER, pname VARCHAR2(15));
CREATE TABLE parts2 (pnum INTEGER, pname VARCHAR2(15));
DECLARE
TYPE NumTab IS TABLE OF parts1.pnum%TYPE INDEX BY PLS_INTEGER;
TYPE NameTab IS TABLE OF parts1.pname%TYPE INDEX BY PLS_INTEGER;
pnums NumTab;
pnames NameTab;
--Not big enough: iterations CONSTANT PLS_INTEGER := 500;
iterations CONSTANT PLS_INTEGER := 500000;
t1 INTEGER;
t2 INTEGER;
t3 INTEGER;
BEGIN
FOR j IN 1..iterations LOOP -- load index-by tables
pnums(j) := j;
pnames(j) := 'Part No. ' || TO_CHAR(j);
END LOOP;
t1 := DBMS_UTILITY.get_time;
FOR i IN 1..iterations LOOP -- use FOR loop
INSERT INTO parts1 VALUES (pnums(i), pnames(i));
END LOOP;
t2 := DBMS_UTILITY.get_time;
FORALL i IN 1..iterations -- use FORALL statement
INSERT INTO parts2 VALUES (pnums(i), pnames(i));
t3 := DBMS_UTILITY.get_time;
DBMS_OUTPUT.PUT_LINE('Execution Time (secs)');
DBMS_OUTPUT.PUT_LINE('---------------------');
DBMS_OUTPUT.PUT_LINE('FOR loop: ' || TO_CHAR((t2 - t1)/100));
DBMS_OUTPUT.PUT_LINE('FORALL: ' || TO_CHAR((t3 - t2)/100));
COMMIT;
END;
/
The other modification that needs to be made is the addition of the start and stop calls for DBMS_PROFILER. Add in “DBMS_PROFILER.START_PROFILER(sysdate, ‘where’);” and “DBMS_PROFILER.STOP_PROFILER” statements where appropriate and repeat the run (create the tables once, then add delete or truncate statements at the beginning). In the example below (only the BEGIN section is shown, DECLARE stays the same except for adding two “run” variables), I added three captures: time to load the tables, and times for each of the inserts.
run_comment VARCHAR2(30) := to_char(sysdate);
run_comment1 VARCHAR2(30);
BEGIN
DBMS_PROFILER.START_PROFILER
(run_comment,'Load tables');
FOR j IN 1..iterations LOOP -- load index-by tables
pnums(j) := j;
pnames(j) := 'Part No. ' || TO_CHAR(j);
END LOOP;
DBMS_PROFILER.STOP_PROFILER;
t1 := DBMS_UTILITY.get_time;
DBMS_PROFILER.START_PROFILER(run_comment,'For Loop');
FOR i IN 1..iterations LOOP -- use FOR loop
INSERT INTO parts1 VALUES (pnums(i), pnames(i));
END LOOP;
DBMS_PROFILER.STOP_PROFILER;
t2 := DBMS_UTILITY.get_time;
DBMS_PROFILER.START_PROFILER(run_comment,'Forall');
FORALL i IN 1..iterations -- use FORALL statement
INSERT INTO parts2 VALUES (pnums(i), pnames(i));
DBMS_PROFILER.STOP_PROFILER;
t3 := DBMS_UTILITY.get_time;
DBMS_OUTPUT.PUT_LINE('Execution Time (secs)');
DBMS_OUTPUT.PUT_LINE('---------------------');
DBMS_OUTPUT.PUT_LINE('FOR loop: ' || TO_CHAR((t2 - t1)/100));
DBMS_OUTPUT.PUT_LINE('FORALL: ' || TO_CHAR((t3 - t2)/100));
COMMIT;
END;
/
What are the results? We could query the tables directly, but that’s not very interesting. Using Toad, the data appears as so:
Kind of dull in a way. But there is a way to have the format shown in HTML. MetaLink note 243775.1, “Implementing and Using the PL/SQL Profiler,” contains a downloadable ZIP file, with a SQL script to take a run and generate HTML output. You can run profiler.sql as is and enter a run ID, or pass the ID in as a parameter.
SQL> @profiler
RUNID RUN_DATE RUN_COMMENT
---------- ------------------ -----------
2 17-SEP-08 17:36:28 17-SEP-08
3 17-SEP-08 17:36:29 17-SEP-08
4 17-SEP-08 17:36:59 17-SEP-08
Usage:
sqlplus apps/
The script generates a file named profiler_X.html, with X being the selected run ID. The contents of run ID 3 (using the for loop) appear as so:
Looks better, but what happened statement by statement? For that, we need to add in one item, and that is compile with debug. I’ll turn the anonymous block into a procedure and capture the start and stop overall for the looping process (don’t care about the inserts as that is overhead for both parts).
CREATE or REPLACE PROCEDURE profiler_demo
AS
TYPE NumTab IS TABLE OF parts1.pnum%TYPE INDEX BY PLS_INTEGER;
TYPE NameTab IS TABLE OF parts1.pname%TYPE INDEX BY PLS_INTEGER;
pnums NumTab;
pnames NameTab;
iterations CONSTANT PLS_INTEGER := 500000;
t1 INTEGER;
t2 INTEGER;
t3 INTEGER;
run_comment VARCHAR2(30) := to_char(sysdate);
run_comment1 VARCHAR2(30);
BEGIN
execute immediate 'truncate table parts1 reuse storage';
execute immediate 'truncate table parts2 reuse storage';
FOR j IN 1..iterations LOOP -- load index-by tables
pnums(j) := j;
pnames(j) := 'Part No. ' || TO_CHAR(j);
END LOOP;
t1 := DBMS_UTILITY.get_time;
DBMS_PROFILER.START_PROFILER(run_comment,'Start of Loops');
FOR i IN 1..iterations LOOP -- use FOR loop
INSERT INTO parts1 VALUES (pnums(i), pnames(i));
END LOOP;
t2 := DBMS_UTILITY.get_time;
FORALL i IN 1..iterations -- use FORALL statement
INSERT INTO parts2 VALUES (pnums(i), pnames(i));
DBMS_PROFILER.STOP_PROFILER;
t3 := DBMS_UTILITY.get_time;
DBMS_OUTPUT.PUT_LINE('Execution Time (secs)');
DBMS_OUTPUT.PUT_LINE('---------------------');
DBMS_OUTPUT.PUT_LINE('FOR loop: ' || TO_CHAR((t2 - t1)/100));
DBMS_OUTPUT.PUT_LINE('FORALL: ' || TO_CHAR((t3 - t2)/100));
COMMIT;
END;
/
Compile the procedure with DEBUG (alter procedure profiler_demo compile_debug) and then run profiler again. The output will be exactly what we’re looking for in terms of line by line detail and timings. You have to admit this is MUCH better than pouring through a formatted trace file to see where all of the time went.
In Closing The DBMS_PROFILER package has been around since at least Oracle8i days. For some reason, it seems to get short shrift in many of the tuning books and online discussion forums. After seeing how easy it is to implement, perhaps you’ll add this built-in to your tuning arsenal. Other variations of using this include joining the profiler tables to USER_SOURCE or ALL_SOURCE yourself (which is what takes place in the profiler script; use that as a starting point if so desired), drilling down to the worst or most time consuming lines, and using three scripts contained in the plsql/demo directory (profdemo, profrep and profsum.sql). Also buried inside the demo directory are the remnants of a Java-based viewing utility. There was a profview.html in older versions (not present at least in 10g Windows), a Windows batch file (profview_nt.bat), and a jar file (profview.jar). The batch file will error out with a failure to load the Main-Class manifest from profview.jar. The command to run the viewer (assuming you can fix the manifest error, or happen to have an older version of this and want to modernize it) is java riw. Overall, the HTML output is probably the easiest and quickest means of viewing the profile information in a better-than-text-only interface.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() |
![]()