You are on page 1of 10

ORACLE PL/SQL TUNING

Alan Silberman, Aluar Aluminio Argentino S.A.I.C.


Introduction
Numerous books and articles can be found regarding SQL statement tuning, but sometimes the problem lies in the surrounding Oracle PL/SQL code. Of all different PL/SQL tuning techniques, specifically: procedural code optimization, SQL versus PL/SQL, explicit versus implicit cursors, PL/SQL tables, packages, PLS_INTEGER data type, NOT NULL constraints, data type conversions, client/server network traffic, constraints, triggers, functions, stored procedures, packages, pins, and array processing, due to space constraints this presentation will only review procedural code optimization techniques that can be applied to PL/SQL.

1. PL/SQL tuning
PL/SQL (Procedural Language/Structured Query Language) is a set of procedural features that extends the power of the SQL language. PL/SQL statements can be combined with traditional SQL statements in various Oracle products to ease application programming and improve application performance. PL/SQL is essentially a procedural extension of SQL. It allows all procedural constructions that are available in traditional third generation languages. Having decided to implement certain processing in PL/SQL, either as SQL code replacement or to implement certain complex logic in an application, there are frequent opportunities to improve the performance of such code: Procedural constructs in PL/SQL (iteration, decision and sequence) are subject to many of the appropriate optimization techniques available for other procedural languages. To store PL/SQL routines in the database reduces PL/SQL parsing overhead. Certain specific PL/SQL techniques can be used to improve database access, for example, explicit cursors, CURRENT OF CURSOR, PL/SQL tables, etc. There are many techniques available for PL/SQL code performance optimization. It is key to understand which techniques deliver the best performance improvements. Due to the frequent scarcity of resources (people, time) it is central to focus first in areas that bring the best opportunities of improvement. Recommendation SQL statement tuning Reduce iterations (and iteration duration) in PL/SQL blocks Use PL/SQL tables Store PL/SQL objects in the database Execute server-side PL/SQL programs Limit the use of dynamic SQL Find similar SQL statements and SQL statements that do not use bind variables Use explicit instead of implicit cursors Use temporary tables in the database Use ROWID for iterative processing Order IF statements based on the frequency on which the condition occurs Reduce SYSDATE calls Reduce use of MOD function Use PLS_INTEGER data type instead of NUMBER for integer operations Ensure the same data type in both operands in a comparison operation Impact Large Large Large Large Large Large Intermediate Intermediate Intermediate Intermediate Small Small Small Small Small

This paper will analyze only procedural code optimization that can be applied to PL/SQL.

2. Optimization of procedural code


www.odtug.com ODTUG Kaleidoscope 2006

Oracle PL/SQL Tuning

Silberman

All good programming practices relevant for procedural languages apply to PL/SQL, which is the nearest resembling a procedural language that Oracle offers. As such, PL/SQL is subject to the same optimization principles that are applied to other procedural languages. In some circumstances pure PL/SQL code (without database access) can consume excessive CPU resources. Some of the basic principles for optimizing PL/SQL that are applicable to other procedural languages are: Optimize or minimize loop processing. Optimize conditional statements. Optimize sequences. Avoid recursion.

2.1. Optimize loop processing


Experience shows that most of the execution time in programs that do not have a strong incidence of I/O operations is concentrated in approximately 3% of the source code. It is frequently seen a small internal loop, whose speed determines the programs global speed. If one could accelerate that internal loop by 10%, the whole program could be accelerated by almost 10%. And if the internal loop has less than 10 statements, a brief analysis probably could reduce it to 9 statements or even less. Any inefficiency inside a loop will be magnified by its multiple executions. It is recommended to analyze each operation in a critical internal loop, trying to modify both the program and its data structure, in a way such to be able to remove some of the operations. The advantages of this approach are: It does not take much time, since the internal loop is small. The benefit is real. PL/SQL programs that include loops are candidates for performance optimization. A frequent mistake is to put static code (or code that does not change in each loop iteration) inside the loop. If these situations can be identified, it is recommended to extract the static code, assign its output to one or more variables, and reference them inside the loop. The following code fragment calculates a constant value 100,000 times:
declare checksum number := 0.0; a number := 1.0; b number := 2.0; c number := 3.0; d number := 4.0; e number := 5.0; begin for r in 1..100000 loop checksum := checksum + ((a+b)/(c+d))*e; end loop; end;

Its execution time was recorded to be 1.22 seconds. The following code fragment extracts the calculation of the constant value and puts it outside the loop:
declare checksum a number b number c number d number e number number := 0.0; := 1.0; := 2.0; := 3.0; := 4.0; := 5.0;

www.odtug.com

ODTUG Kaleidoscope 2006

Oracle PL/SQL Tuning


factor number := ((a+b)/(c+d))*e; begin for r in 1..100000 loop checksum := checksum + factor; end loop; end;

Silberman

Its execution time was recorded to be 0.18 seconds (85.24% less). Morale: if an expression remains constant or static in all loop executions, that expression should not be inside the loop. Another example follows. Lets assume the following procedure that summarizes book revisions, executing daily at 8:00 A.M. and taking 15 minutes to complete its execution.
CREATE OR REPLACE PROCEDURE summarize_reviews ( summary_title_in IN VARCHAR2, isbn_in IN book.isbn%TYPE) IS CURSOR review_cur IS SELECT text, TO_CHAR (SYSDATE, 'MM/DD/YYYY') today FROM book_review WHERE isbn = isbn_in; BEGIN FOR review_rec IN review_cur LOOP IF LENGTH (review_rec.text) > 100 THEN review_rec.text := SUBSTR (review_rec.text, 1, 100); END IF; review_pkg.summarize ( UPPER (summary_title_in), today, UPPER (review_rec.text) ); END LOOP; END; /

There are many problems with the previous code: Since the job begins and ends the same day, it is unnecessary to perform a SELECT from SYSDATE for each row in the query. The review_rec record is updated. Although this is allowed by PL/SQL, it is best to treat the loop index as a constant. Since the summary_title_in argument never changes, the UPPER function should not be used in each loop iteration. Instead of checking the text length for each row and then using the SUBSTR and UPPER functions, the SQL SUBSTR function can be used. Generally speaking, the performance of built-in functions (such as SUBSTR) is better in SQL than in PL/SQL. Rewriting the code making use of previous comments we could obtain:
CREATE OR REPLACE PROCEDURE summarize_reviews ( summary_title_in IN VARCHAR2, isbn_in IN book.isbn%TYPE) IS l_summary book_types.summary_t := UPPER (summary_title_in); l_today CONSTANT DATE := TRUNC (SYSDATE); CURSOR review_cur IS SELECT UPPER (SUBSTR (text, 1, 100)) text

www.odtug.com

ODTUG Kaleidoscope 2006

Oracle PL/SQL Tuning


FROM book_review WHERE isbn = isbn_in; BEGIN FOR review_rec IN review_cur LOOP review_pkg.summarize ( l_summary, l_today, review_rec.text ); END LOOP; END; /

Silberman

The LOOP END LOOP clauses are used to execute statements inside the loop structure repeatedly. A badly constructed loop can have a drastic effect on performance. The two important principles in loop optimization are: Try to minimize the number of iterations in the loop. Each iteration consumes CPU resources, so that if the loop has ended, the EXIT statement should be used to exit the loop if there is no need to continue in it. If EXIT or RETURN are used inside a WHILE or FOR loop, a non-structured loop termination is generated, which is against legibility and results in code that is more difficult to trace. Reduce the processing time inside the loop. It should be ensured that no statements remain inside a loop that can be executed safely outside the loop. If a statement does not reference a loop variable, maybe it could be executed outside the loop, and maybe once, instead of many times. The following code fragment shows a poorly designed loop:
1 2 3 4 5 6 7 8 9 10 11 12 13 for counter1 in 1..500 loop for counter2 in 1..500 loop modcounter1 := MOD(counter1,10); /* modcounter2 := MOD(counter2,10); /* sqrt1 := SQRT(counter1); /* sqrt2 := SQRT(counter2); /* if modcounter1 = 0 then if modcounter2 = 0 then ... do something with sqrt1 y end if; end if; end loop; end loop;

executes executes executes executes sqrt2;

250,000 250,000 250,000 250,000

times times times times

*/ */ */ */

The execution time was recorded to be 5.19 seconds. Some of the problems with the previous code are: In spite of the fact that we only want to process numbers that can be divided by 10 (that is what the MOD 10 function does), we are performing the loop 500 times instead of the 50 times we need. The FOR statement in PL/SQL can not iterate in multiples of 10. However, by using FOR, we are executing the statements inside each loop 10 times more frequently than is strictly needed. Statements in lines 3 and 5 are included inside the internal loop. This means that they are executed once for each iteration of that internal loop, although inside of that internal loop, the value of counter1 does not change. They are executed 500 times more frequently than needed. The following code fragment shows an optimization over the previous code:
1 while counter1 <= 500 loop 2 sqrt1 := SQRT(counter1); /* executes 50 times */ 3 while counter2 <= 500 loop 4 sqrt2 := SQRT(counter2); /* executes 2.500 times */ 5 ... do something with sqrt1 y sqrt2; 6 counter2 := counter2 + 10; 7 end loop; 8 counter1 := counter1 + 10; 9 end loop;

The execution time was recorded to be less than 0.01 seconds (99.80% less). www.odtug.com 4 ODTUG Kaleidoscope 2006

Oracle PL/SQL Tuning

Silberman

In this example, the WHILE statement is used and both loop counters are manually incremented by 10 units. Consequently, the internal loop is executed 50 times for each time the external loop executes. The MOD functions are no longer needed and the statement in line 2 has been moved from the internal loop to the external loop, reducing its number of executions from 250,000 to 50 in this case. In PL/SQL loops, the EXIT statements exits the loop going the statement that follows immediately the END LOOP statement.
EXIT WHEN condition;

Is more intuitive than and equivalent to:


IF condition then EXIT; END IF;

All exit logic in a loop should be consolidated in a single EXIT statement. A last example refers to trying to improve the following code:
IF NOT is_number (stg) THEN stg := remove_punctuation (stg); END IF;

The functionality of the previous code fragment is the following: if the stg string is a number, then a function is called, that eliminates all punctuation symbols from the character string. The is_number function seems to be a PL/SQL built-in function, but it is not built-in by Oracle, so it must be a low-level function built by some developer. Since this function could be executed hundreds, thousands or millions of times daily, it is reasonable to check it first. The following function shows the current implementation of the is_number function residing in the database.
FUNCTION is_number (word_in IN VARCHAR2) RETURN BOOLEAN IS BOOL_RC boolean; ASCII_CHAR_VAL number; WORD_LENGTH number; CHAR_POS number; BEGIN BOOL_RC := TRUE; WORD_LENGTH := LENGTH(WORD_IN); for CHAR_POS in 1..WORD_LENGTH loop ASCII_CHAR_VAL := ASCII(SUBSTR(WORD_IN, CHAR_POS, 1)); if ASCII_CHAR_VAL < 48 or ASCII_CHAR_VAL > 57 and ASCII_CHAR_VAL != 46 then BOOL_RC := FALSE; return BOOL_RC; end if; end loop; return BOOL_RC; END is_number;

The is_number function does the following: for each of the N characters in the string, it isolates the character using SUBSTR() and determines whether it is a digit or a point. The developer made use of the ASCII() function to convert the character to its corresponding ASCII value, and then checks whether that number falls in the range of allowed values. Numbers can only have characters whose ASCII value is 46 (point) or falls in the range between 48 and 57 inclusive. The is_number function fulfills the functional requirements, but is the current implementation of is_number the best possible implementation?

www.odtug.com

ODTUG Kaleidoscope 2006

Oracle PL/SQL Tuning

Silberman

Firstly, it should be analyzed whether it is really necessary to scan each of the characters in the string. The longer the string is, the more processing must be performed by the function. This means that the performance of is_string depends greatly on the value that it is read, and that does not seem reasonable. On the other hand, scanning each of the characters requires code (loop, local variables, etc.) and the use of ASCII() to test the value of each individual character is low-level and quite obscure. Is there a way to improve the current implementation of is_number with another approach that does not need to use a loop? Does a PL/SQL built-in function exist that can be helpful to avoid the loop? The solution to this problem is to forget about the string processing functions (and the loop) and use the built-in TO_NUMBER() function to try to convert the character string to a number. If the conversion is successful, a TRUE value would be returned; otherwise an exception would be raised and a FALSE value would be returned. A suggested implementation follows:
FUNCTION is_number (stg_in IN VARCHAR2) RETURN BOOLEAN IS val NUMBER; BEGIN val := TO_NUMBER (stg_in); RETURN TRUE; EXCEPTION WHEN OTHERS THEN RETURN FALSE; END is_number;

Comparing the performance of both implementations, the original implementation takes 9 more times than the optimized alternative.

2.2. Optimize conditional statements


One should avoid the need to make calculations when evaluating expressions. Examples: Original statement
IF comm*12 < 4800 THEN ... IF comm < 4800/12 THEN ...

Recommended statement
IF comm < 400 THEN ... IF comm < 400 THEN ...

When one needs to write conditional logic that includes various mutually exclusive clauses, the ELSIF statement should be used. Example without ELSIF (not optimized):
PROCEDURE process_lineitem (line_in IN INTEGER) IS BEGIN IF line_in = 1 THEN process_line1; END IF; IF line_in = 2 THEN process_line2; END IF; ... IF line_in = 2045 THEN process_line2045; END IF; END;

www.odtug.com

ODTUG Kaleidoscope 2006

Oracle PL/SQL Tuning

Silberman

Each IF statement is executed and each condition is evaluated, in spite of the fact that if one of them is true, all the rest is false. Example with ELSIF (optimized):
PROCEDURE process_lineitem (line_in IN INTEGER) IS BEGIN IF line_in = 1 THEN process_line1; ELSIF line_in = 2 THEN process_line2; ... ELSIF line_in = 2045 THEN process_line2045; END IF; END;

ELSIF is the most efficient implementation to process mutually exclusive clauses. As soon as one clause is detected to be true, all remaining clauses are ignored. When processing a PL/SQL block containing IF statements with multiple nested conditions, PL/SQL will consider each specified condition until it finds a condition which is successfully evaluated. This is why the average PL/SQL processing time will be reduced if the most probable condition appears first in the IF block. The following code fragment shows a non-optimized IF statement.
for counter1 in 1..10000 loop if counter1 < 10 then ... do a; elsif counter1 < 20 then ... do b; elsif counter1 < 30 then ... do c; elsif counter1 < 40 then ... do d; elsif counter1 < 50 then ... do e; elsif counter1 < 60 then ... do f; elsif counter1 < 70 then ... do g; elsif counter1 < 80 then ... do h; elsif counter1 < 90 then ... do i; else /* counter1 >= 90 */ ... do j; end if; end loop;

Its execution time was recorded to be 0.06 seconds. The first condition (counter1 < 10) will be true only in 9 out of the 10,000 loop executions (0.09% of the times). The last implicit condition (counter1 >= 90) will be true in 9,910 of the 10,000 loop executions (99.10% of the times), but in this 99.10% of the cases all 9 previous comparisons will be unnecessarily executed. The total number of comparisons in this case is 89,639, which is the same to say as 8.96 comparisons for each loop execution. The following code fragment shows the optimized IF statement.
for counter1 in 1..10000 loop

www.odtug.com

ODTUG Kaleidoscope 2006

Oracle PL/SQL Tuning


if counter1 >= ... do j; elsif counter1 ... do a; elsif counter1 ... do b; elsif counter1 ... do c; elsif counter1 ... do d; elsif counter1 ... do e; elsif counter1 ... do f; elsif counter1 ... do g; elsif counter1 ... do h; elsif counter1 ... do i; end if; end loop; 90 < 10 then < 20 then < 30 then < 40 then < 50 then < 60 then < 70 then < 80 then < 90 then

Silberman

Its execution time was recorded to be 0.02 seconds (66.66% less). The expression which is more frequently satisfied (99.10% of the times) appears first in the IF structure. For the majority of the iterations (99.10% of the times) this is the only evaluation that is needed. The total number of comparisons in this case is 10,448, which is the same to say as 1.04 comparisons for each loop execution. All multiple conditions shall be put in decreasing order of probability. The most probable condition shall be the first that is evaluated in the block. It must be considered that probabilities are not necessarily static, but they can vary with time, due to a certain distribution of values. Examples are: gender distribution in percentages, market share, state distribution of customers in percentages, etc.). Sometimes one can stumble upon code such as the following:
DECLARE boolean_variable1 BOOLEAN; boolean_variable2 BOOLEAN; BEGIN IF boolean_variable1 = TRUE THEN ... ELSIF boolean_variable2 = FALSE THEN ... END IF;

The previous code can be simplified removing comparisons:


DECLARE boolean_variable1 BOOLEAN; boolean_variable2 BOOLEAN; BEGIN IF boolean_variable1 THEN ... ELSIF NOT boolean_variable2 THEN ... END IF;

2.3. Optimize sequences


www.odtug.com 8 ODTUG Kaleidoscope 2006

Oracle PL/SQL Tuning

Silberman

Sequence optimization is based in the fact that conditionality that is not represented in the code may exist, that turns unnecessary the execution of certain part of the code. Lets examine the following Forms trigger that retrieves information for a row of the master block and many rows of the detail block.
EXECUTE_QUERY; NEXT_BLOCK; EXECUTE_QUERY; PREVIOUS_BLOCK;

If the first EXECUTE_QUERY does not return any rows, there is no reason to execute the following statements. The recommendation is to write the code in the following way:
EXECUTE_QUERY; IF NOT FORM_SUCCESS THEN RAISE FORM_TRIGGER_FAILURE; END IF; NEXT_BLOCK; EXECUTE_QUERY; PREVIOUS_BLOCK;

In some cases, sequence optimization has to deal with the possibility of eliminating an IF statement. Consider the next conditional sequence:
IF hiredate < SYSDATE THEN date_in_past := TRUE; ELSE date_in_past := FALSE; END IF;

If it has already been validated that HIREDATE cannot be NULL, the IF statement can be replaced with the following assignment:
date_in_past := hiredate < SYSDATE;

If HIREDATE can be NULL, the original IF statement can be replaced with the following assignment:
date_in_past := NVL(hiredate < SYSDATE, FALSE);

2.4. Avoid recursion


A recursive routine is one that invokes itself. The classical case is the routine calculating an integer numbers factorial. An implementation follows in PL/SQL, a language that allows both recursion and reentering routines.
FUNCTION factorial (fac INTEGER) RETURN INTEGER IS BEGIN IF fac = 1 THEN RETURN 1; ELSE RETURN (fac * factorial (fac - 1)); END IF; END;

www.odtug.com

ODTUG Kaleidoscope 2006

Oracle PL/SQL Tuning

Silberman

Recursive routines frequently offer elegant solutions to complex programming problems, but they tend to consume great amounts of memory and tend to be less efficient than a non-recursive alternative. Many recursive algorithms can be rewritten using non-recursive techniques. If it were possible, non-recursive routines shall be favored in place of recursive ones to improve performance.

2.5. Conclusions
After performing a SQL statement tuning exercise, most of the times a similar PL/SQL statement tuning exercise is not performed. In many cases, though, such an exercise will deem benefits in the realms of performance. A summary of the recommendations mentioned throughout this paper follows. Optimize loop processing o Process all invariant code outside of loops. o Minimize iterations of the loop. Optimize conditional code. o In multiway decisions (CASE, SWITCH, etc.) place the most probable condition first. o In complex logic conditions, test the most probable condition first. Optimize sequences. o Insert conditional statements. o Remove conditional statements. Avoid recursions. Other. o Remove unused expressions, variables and labels. o Use integer arithmetic instead of floating point arithmetic when possible. o Avoid mixed data types in arithmetic and logic operations to eliminate unnecessary data type conversions.

Other PL/SQL performance optimization techniques, such as SQL versus PL/SQL, explicit versus implicit cursors, PL/SQL tables, packages, PLS_INTEGER data type, NOT NULL constraints, data type conversions, client/server network traffic, constraints, triggers, functions, stored procedures, packages, pins, array processing and profiling were outside the scope of this paper and will be the subject of further papers.

Bibliography
Code fragments were cited from the following chronologically sorted references: [Knuth 1974] [Frazzini 1993] [Gurry 1996] [Urman 1996] [Trezzo 1996] [Angelis 1997] [Harrison 1997] [Feuerstein 1997] Knuth, Donald, Structured Programming with Go To Statements, ACM Computing Surveys, volume 6, number 4, December 1974. Frazzini, John, Kane, Valerie K. & Remsen, Mary, Tune Oracle7 Applications Participant Guide, Oracle Corporation, USA, 1993. Gurry, Mark & Corrigan, Peter, Oracle Performance Tuning, second edition, OReilly & Associates, Inc., USA, 1996. Urman, Scott, Oracle PL/SQL Programming, Osborne McGraw-Hill, Berkeley, USA, 1996. Trezzo, Joseph C., A DBA perspective on procedures, functions, packages, and triggers, part 2, Exploring Oracle DBMS, USA, September 1996. Angelis, Nick, Developer/2000: Performance Tuning Student Guide, Oracle Corporation, USA, 1997. Harrison, Guy, Tuning Oracle PL/SQL, Select, International Oracle Users Group Americas, volume 5, number 1, October 1997. Feuerstein, Steven & Pribyl, Bill, Oracle PL/SQL Programming, second edition, OReilly & Associates, Inc., USA, 1997.

www.odtug.com

10

ODTUG Kaleidoscope 2006

You might also like