Boost Performance with PL/SQL Programming Best Practices Tim Hall Oracle ACE Director Oracle ACE of the Year 2006 OCP DBA (7, 8, 8i, 9i, 10g, 11g) OCA PL/SQL Developer http://www.oracle-base.com Oracle PL/SQL Tuning (Rampant) Oracle Job Scheduling (Rampant) http://www.oracle-base.com http://www.oracle-base.com Bind Variables • Oracle performs a CPU intensive hard parse for all new statements. Unnecessary parses waste CPU and memory. • Statements already present in the shared pool only require a soft parse. • Statement matching uses “Exact Text Match”, so literals, case and whitespaces are a problem. SELECT * FROM emp WHERE empno = 1; SELECT * FROM emp WHERE empno = 2; SELECT * FROM emp WHERE empno = :p_empno; • Make sure client application use bind variables when calling your code. • Use CURSOR_SHARING parameter when you can’t alter code. ALTER SESSION SET cursor_sharing = force; • Be careful when using dynamic SQL. http://www.oracle-base.com Overview of the PL/SQL Engine PL/SQL Engine PL/SQL Block PL/SQL Block Procedural Statement Executor SQL Statement Executor Oracle Server • • • • • PL/SQL contains procedural and SQL code. Each type of code is processed separately. Switching between code types causes an overhead. The overhead is very noticeable during batch operations. Bulk binds minimize this overhead. http://www.oracle-base.com Bulk-Binds – BULK COLLECT • Populate collections directly from SQL using BULK COLLECT. • Demo • Collections are held in memory, so watch collection sizes. • Demo • Implicit array processing introduced in 10g. • Demo SELECT * BULK COLLECT INTO l_tab FROM tab1; OPEN c1; LOOP FETCH c1 BULK COLLECT INTO l_tab LIMIT 1000; EXIT WHEN l_tab.count = 0; -- Process chunk. END LOOP; CLOSE c1; FOR cur_rec IN (SELECT * FROM tab1) LOOP -- Process row. END LOOP; http://www.oracle-base.com Bulk-Binds – FORALL • Bind data in collections into DML using FORALL. • Demo • Use INDICIES OF and VALUES OF for sparse collections. • Use SQL%BULK_ROWCOUNT to return the number of rows affected by each statement. • • FORALL i IN l_tab.FIRST .. l_tab.LAST INSERT INTO tab2 VALUES l_tab(i); The SAVE EXCEPTIONS allows bulk operations to complete. Exceptions captured in SQL%BULK_EXCEPTIONS. http://www.oracle-base.com Short-Circuit Evaluations and Logic Order • If left side of an OR expression is TRUE, the whole expression is TRUE. TRUE OR FALSE = TRUE TRUE OR TRUE = TRUE • If the left side of an AND expression is FALSE, the whole expression is FALSE. FALSE AND FALSE = FALSE FALSE AND TRUE = FALSE • • • • In these cases Oracle doesn’t evaluate the second half of the expresson. Place “least expensive” tests to the left of expressions. Evaluations of ELSIF and CASE statements stops once a match is found. Place the “most likely outcomes” at the top of branching constructs. IF l_continue OR fn_rec_count > 10 THEN -- Do Something END IF; IF l_continue AND fn_rec_count > 10 THEN -- Do Something END IF; IF l_rec_type = 'POPULAR' THEN -- Do Something ELSIF l_rec_type = 'MEDIUM' THEN -- Do Something ELSIF l_rec_type = ‘UNPOPULAR' THEN -- Do Something END IF; CASE l_rec_type WHEN 'POPULAR' THEN -- Do Something WHEN 'MEDIUM' THEN -- Do Something WHEN ‘UNPOPULAR' THEN -- Do Something END CASE; http://www.oracle-base.com Declarations in Loops • Code within loops gets run multiple times. • Variable declarations and procedure/function calls in loops impact on performance. • Simplify code within loops to improve performance. • Oracle 11g offsets solves some of the performance impact with automatic subprogram inlining. • Don’t stop using modular code because of this. Keep these results in context! -- Bad idea. FOR i IN 1 .. 100 LOOP DECLARE l_str VARCHAR2(200); BEGIN -- Do Something. END; END LOOP; -- Better idea. DECLARE l_str VARCHAR2(200); BEGIN FOR i IN 1 .. 100 LOOP -- Do Something. END LOOP; END; http://www.oracle-base.com Efficient Function Calls • When functions are called in SQL statements, minimize the number of calls by filtering the data if possible. • Demo • When function calls are present in the WHERE clause, consider function-based indexes. • Demo • Consider maintenance costs, disk space requirements and global affect of function-based indexes. SELECT SQRT(num_val), COUNT(*) AS amount FROM tab1 GROUP BY SQRT(num_val); SELECT SQRT(num_val), amount FROM (SELECT num_val, COUNT(*) AS amount FROM tab1 GROUP BY num_val)) CREATE INDEX efficient_functions_fbidx ON efficient_functions (SQRT(data_length)); SELECT COUNT(*) FROM efficient_functions ef WHERE SQRT(ef.data_length) = 5.47722558; http://www.oracle-base.com Using the NOCOPY Hint • The NOCOPY hint allows OUT and IN OUT parameter to be passed by-reference, rather than by-value. PROCEDURE myproc (p_tab BEGIN -- Do something. END; IN OUT NOCOPY CLOB) IS • By-value: Procedure uses temporary buffer. Copies value back on successful completion. • By-reference: Procedure uses original memory location directly. • Beware of affect of error handling and parameter aliasing on parameter values. • It’s a hint, not a directive, so it can be ignored http://www.oracle-base.com PLSQL_OPTIMIZE_LEVEL • The PLSQL_OPTIMIZE_LEVEL parameter was introduced in 10g to control how much optimization the compiler performs: – 0 : Code will compile and run in a similar way to 9i and earlier. New actions of BINARY_INTEGER and implicit array processing lost. – 1 : Performs a variety of optimizations, including elimination of unnecessary computations and exceptions. Does not alter source order. – 2 : Performs additional optimizations, including reordering source code if necessary. The is the default setting in 10g and 11g. – 3 : New in 11g. Yet more optimizations and subprogram inlining. • The optimization level associated with the library unit is visible using the %_PLSQL_OBJECT_SETTINGS view. • Adjust only if package load times are adversely affected. ALTER SESSION SET PLSQL_OPTIMIZE_LEVEL=0; ALTER PROCEDURE my_big_package COMPILE; http://www.oracle-base.com Conditional Compilation • • • • • Conditional compilation was introduced in 10g to allow source to be tailored to specific environments using compiler directives. Compiler flags are identified by the “$$” prefix. Conditional control is provided by the $IF-$THEN-$ELSE-$END syntax. The database source contains all the directives, but the post-processed source is displayed using the DBMS_PREPROCESSOR package. The PLSQL_CCFLAGS clause is used to set the compiler flags. The compiler flags associated with the library unit is visible using the %_PLSQL_OBJECT_SETTINGS view. http://www.oracle-base.com CREATE OR REPLACE PROCEDURE debug (p_text IN VARCHAR2) AS $IF $$debug_on $THEN l_text VARCHAR2(32767); $END BEGIN $IF $$debug_on $THEN DBMS_OUTPUT.put_line(p_text); $ELSE NULL; $END END debug; ALTER PROCEDURE debug COMPILE PLSQL_CCFLAGS = 'debug_on:TRUE' REUSE SETTINGS; CREATE OR REPLACE PROCEDURE debug (p_text IN VARCHAR2) AS l_text VARCHAR2(32767); BEGIN DBMS_OUTPUT.put_line(p_text); END debug; CREATE OR REPLACE PROCEDURE debug (p_text IN VARCHAR2) AS BEGIN NULL; END debug; Native Compilation of PL/SQL • By default PL/SQL is interpreted. • Set PLSQL_CODE_TYPE parameter to NATIVE before creating or compiling code. ALTER SESSION SET PLSQL_CODE_TYPE=NATIVE; ALTER PROCEDURE my_proc COMPILE; • Prior to 11g, native compilation converts PL/SQL to C, which is then compiled in shared libraries. • Improves performance of procedural logic. • Demo • Doesn’t affect the speed of database calls. • The PLSQL_CODE_TYPE associated with the library unit is visible using the %_PLSQL_OBJECT_SETTINGS view. http://www.oracle-base.com INTEGER Types • NUMBER and it’s subtypes use an Oracle internal format, rather than the machine arithmetic. • INTEGER and other constrained type need additional runtime checks compared to NUMBER. • PLS_INTEGER uses machine arithmetic to reduce overhead. • BINARY_INTEGER is slow in 8i and 9i, but fast in 10g because it uses machine arithmetic. • Demo • 11g includes SIMPLE_INTEGER which is quick in natively compiled code. • Use the appropriate datatype for the job. http://www.oracle-base.com BINARY_FLOAT and BINARY_DOUBLE • New in 10g. • They use machine arithmetic, like PLS_INTEGER and BINARY_INTEGER. • Require less storage space. • Fractional values not represented precisely, so avoid when accuracy is important. • Approximately twice the speed of NUMBER. • Demo • Use the appropriate datatype for the job. http://www.oracle-base.com Avoid unnecessary PL/SQL • SQL is usually quicker than PL/SQL. • Don’t use UTL_FILE to read text files if you can use external tables. • Don’t write PL/SQL merges if you can use the MERGE statement. • Use multi-table inserts, rather than coding them manually. • Use DML error logging (DBMS_ERRLOG) to trap failures in DML, rather than coding PL/SQL. • All use DML, which is easily parallelized. http://www.oracle-base.com Quick Points • Use ROWIDs for update when data is selected for subsequent update. • Use built-in functions where possible. They are usually more efficient that your custom code. • Datatype conversions take time. Reduce them. • Implicit cursors are faster and do more exception checking than explicit cursors. Use them. • Hide performance problems by decoupling. Queue requests and process in batch. http://www.oracle-base.com Best Practices Summary • • • • • • • • • • • • • • • • Use bind variables to reduce the CPU and memory overheads associated with parsing similar statements multiple times. Use bulk-binds. Order logical expressions and branching structures to increase the speed of code. Be mindful of the complexity of code blocks inside loops. Reduce unnecessary function calls from SQL statements. Use the NOCOPY hint. Use PLSQL_OPTIMIZE_LEVEL for performance improvements and decreased load times. Remove unnecessary code using conditional compilation. Natively compile PL/SQL to improve the speed of procedural code. Use PLS_INTEGER, BINARY_INTEGER, BINARY_FLOAT and BINARY_DOUBLE types instead of internal numeric types. Avoid unnecessary PL/SQL by using leaner equivalents. Improve record access speeds by using internal rowids rather than primary key searches. Don’t duplicate functionality of built-in string functions. Minimize the number of datatype conversions. Use of implicit rather than explicit cursors. Hide performance problems from users by decoupling processes. http://www.oracle-base.com The End… • Questions? • References: http://www.oracle-base.com • Demos: http://www.oracle-base.com/workshops/plsql-best-practices/demos.zip http://www.oracle-base.com