Advanced Oracle PL/SQL Developer's Guide - Second Edition - Sample Chapter
Advanced Oracle PL/SQL Developer's Guide - Second Edition - Sample Chapter
Second Edition
P U B L I S H I N G
Saurabh K. Gupta
Second Edition
ee
Sa
pl
e
P r o f e s s i o n a l
E x p e r t i s e
D i s t i l l e d
Saurabh K. Gupta
P U B L I S H I N G
Preface
How many of us would believe that PL/SQL was introduced as a scripting language
for executing a bunch of SQL scripts? Well, that's true. With the growing need to
build computational logic and condition-based constructs, and to manage exception
rules within databases, Oracle Corporation first released PL/SQL along with Oracle
Database Version 6.0 with a limited set of capabilities. Within its capacity, PL/SQL
was capable of creating program units that could not be stored inside the database.
Eventually, Oracle's release in the application line, SQL *Forms version V3.0,
included the PL/SQL engine and allowed developers to implement the application
logic through procedures. Back then, PL/SQL used to be part of the transaction
processing option in Oracle 6 and the procedural option in Oracle 7. Since the
time of its ingenuous beginning, PL/SQL has matured immensely as a standard
feature of Oracle Database. It has been enthusiastically received by the developer
community, and the credit goes to its support for advanced elements such as
modular programming, encapsulation, support for objects and collections, program
overloading, native and dynamic SQL, and exception handling.
PL/SQL is loosely derived from Ada (named after Ada Lovelace, an English
mathematician who is regarded as the first computer programmer), a high-level
programming language, which complies with the advanced programming elements.
Building a database backend for an application demands the ability to design
the database architecture, skills to code complex business logics, and expertise in
administering and protecting the database environment. One of the principal reasons
why PL/SQL is a key enabler in the development phase is its tight integration
with Oracle's SQL language. In addition to this, it provides a rich platform for
implementing the business logic in the Oracle Database layer and store them as
procedures or functions for subsequent use. As a procedural language, PL/SQL
provides a diverse range of datatypes, iterative and control constructs, conditional
statements, and exception handlers.
Preface
"The only real security that a man can have in the world is a reserve of
knowledge, experience and ability"
- Henry Ford
Preface
Preface
Chapter 8, Tuning the PL/SQL Code, introduces the best practices for tuning PL/SQL
code. It starts with the PL/SQL optimizer and rolls through the benefits of native
compilation, PL/SQL code writing skills, and code evaluation design. This chapter
includes the changes in Oracle 12c with respect to large object handling.
Chapter 9, Result Cache, explains the result caching feature in Oracle Database. It is a
powerful caching mechanism that enhances the performance of SQL queries and
PL/SQL functions that are repeatedly executed on the server. This chapter also
discusses the enhancements made to the feature in Oracle Database 12c.
Chapter 10, Analyzing, Profiling, and Tracing PL/SQL Code, details the techniques
used to analyze, profile, and trace PL/SQL code. If you are troubleshooting PL/
SQL code for performance, you must learn the profiling and tracing techniques.
In an enterprise application environment, these practices are vital weapons in a
developer's arsenal.
Chapter 11, Safeguarding PL/SQL Code against SQL injection, describes ways to protect
your PL/SQL from being attacked. A vulnerable piece of code is prone to malicious
attacks and runs the risk of giving away sensitive information. Efficient code writing
and proofing the code from external attacks can help to minimizing the attack
surface area. In this chapter, you will learn the practices for safeguarding your code
against external threats.
Chapter 12, Working with Oracle SQL Developer, describes the benefits of the Oracle
SQL Developer for developers, database administrators, and architects. This chapter
not only helps you get started with SQL Developer, but also helps you gain a better
understanding of the new features of SQL Developer 4.0 and 4.1.
Subprogram inline
[ 229 ]
PRAGMA INLINE directs the compiler to inline the subprogram calls that are just
succeeding it. For example, in the following PL/SQL block, the subprogram call is
inlined in Line 5 but not in Line 7:
PROCEDURE P_SUM_NUM (P_A NUMBER, P_B NUMBER)
...
BEGIN
PRAGMA INLINE ('P_SUM_NUM', 'YES')
result_1 := P_SUM_NUM (10, 20);
[ 230 ]
/*
/*
/*
/*
/*
Line
Line
Line
Line
Line
1
2
3
4
5
*/
*/
*/
*/
*/
Chapter 8
....
result_2 := P_SUM_NUM (100, 200);
END;
/
/* Line 6 */
/* Line 7 */
/* Line 8 */
/* Line 9 */
It is recommended that you inline those utility subprograms that are frequently
invoked in a PL/SQL block. The intra-unit inlining is traceable through sessionlevel warnings. The session-level warnings can be turned on by setting the PLSQL_
WARNINGS parameter to ENABLE:ALL.
PRAGMA INLINE
PRAGMA is a compiler directive that hints the compiler. Any error in the usage
of PRAGMA results in a compilation error. Note that a PRAGMA, which accepts an
argument, cannot accept a formal argument but always needs an actual argument.
PRAGMA INLINE was introduced in Oracle Database 11g to support subprogram
inlining for better PL/SQL performance. When the compilation parameter PLSQL_
OPTIMIZE_LEVEL is 2, you have to specify the subprogram to be inlined using
PRAGMA INLINE. When PLSQL_OPTIMIZE_LEVEL is 3, the PL/SQL optimizer tries to
inline most of the subprogram calls, without requiring any PRAGMA specification. At
[ 231 ]
PLSQL_OPTIMIZE_LEVEL
Oracle Database 10g introduced the PLSQL_OPTIMIZE_LEVEL initialization parameter
to enable or disable PL/SQL optimization. If enabled, the optimizer deploys several
optimization techniques in accordance with the level. The compilation settings
of a PL/SQL program can be queried from the USER_PLSQL_OBJECT_SETTINGS
dictionary view.
Until Oracle Database 10g, the parameter value could be either 0, 1, or 2. Starting
from Oracle Database 11g, the new optimization level can be enabled by setting
the parameter value to 3. Note that the default value of the parameter is 2. The
parameter value can be modified using the ALTER SYSTEM or ALTER SESSION
statements. Only the PL/SQL programs that are compiled after the parameter
modification are impacted. You can also specify the PLSQL_OPTIMIZE_LEVEL value
at the time of explicit compilation of a program unit. For example, a P_OPT_LVL
procedure can be recompiled using the following statement:
ALTER PROCEDURE p_opt_lvl COMPILE PLSQL_OPTIMIZE_LEVEL=1
/
[ 232 ]
Chapter 8
/*Begin the loop for series calculation*/
FOR J IN 1..p_count
LOOP
/*Set inlining for the local subprogram*/
PRAGMA INLINE (F_CROSS, 'YES');
l_series := l_series + F_CROSS(J,2);
END LOOP;
/*Time consumed to calculate the result*/
DBMS_OUTPUT.PUT_LINE('Execution time:'||TO_CHAR(DBMS_UTILITY.GET_
TIME() - L_TIME));
END;
/
[ 233 ]
Let's execute the procedure with a relatively large input value for a computationintensive operation:
BEGIN
p_sum_series (10000000);
END;
/
Execution time:776
PL/SQL procedure successfully completed.
You can also query the compilation warnings from the USER_ERRORS dictionary
view.
Although PRAGMA was specified, the F_CROSS invocation was not inlined. Therefore, the
subprogram inlining doesn't seem to work when PLSQL_OPTIMIZE_LEVEL is set to 1:
BEGIN
P_SUM_SERIES (10000000);
END;
[ 234 ]
Chapter 8
/
Execution time:710
PL/SQL procedure successfully completed.
Let's retrieve the compilation warnings. Oracle includes a new set of compilation
warnings to demonstrate the inlining flow in the program. The PL/SQL optimizer is
instructed by PRAGMA to inline the subprogram in line 28. At line 8, the function
was removed and its body was merged into the main program. Line 28 shows the
inlining request and action:
SQL> show errors
Errors for PROCEDURE P_SUM_SERIES:
LINE/COL
-------1/1
8/3
28/7
28/7
ERROR
------------------------------------------------PLW-05018: unit P_SUM_SERIES omitted optional AUTHID clause;
default value DEFINER used
PLW-06006: uncalled procedure "F_CROSS" is removed.
PLW-06004: inlining of call of procedure 'F_CROSS' requested
PLW-06005: inlining of call of procedure 'F_CROSS' was done
Well done! It runs in 456ms, which is almost half of its baseline, that is, 2 times better
performance.
[ 235 ]
To see the implicit inlining of the subprogram, let's recreate the P_SUM_SERIES
procedure without the PRAGMA specification:
/*Create a procedure*/
CREATE OR REPLACE PROCEDURE P_SUM_SERIES(p_count NUMBER)
IS
l_series NUMBER := 0;
l_time NUMBER;
/*Declare a local subprogram to return the double of a number*/
FUNCTION F_CROSS (p_num NUMBER) RETURN NUMBER IS
BEGIN
RETURN (p_num * 2);
END F_CROSS;
BEGIN
/*Capture the start time*/
l_time := DBMS_UTILITY.GET_TIME();
/*Begin the loop for series calculation*/
FOR J IN 1..p_count
LOOP
/*Set inlining for the local subprogram*/
l_series := l_series + F_CROSS (J);
END LOOP;
/*Time consumed to calculate the result*/
DBMS_OUTPUT.PUT_LINE('Execution time:'||TO_CHAR(DBMS_UTILITY.GET_
TIME() - L_TIME));
END;
/
SP2-0805: Procedure altered with compilation warnings
[ 236 ]
Chapter 8
Note the warnings at line 8 and line 24. No PRAGMA specified, but the PL/SQL
optimizer inlines the subprogram. Let's see how the procedure execution is impacted
by level 3 optimization:
BEGIN
P_SUM_SERIES (10000000);
END;
/
Execution time:420
PL/SQL procedure successfully completed.
The execution time is almost equal to the level 2 optimization, because the technique
to optimize the code was similar in both the cases.
The consolidation of the execution numbers is shown in the following table:
PLSQL_OPTIMIZE_LEVEL
Inlining
Requested
Inlining
Done
Execution
time
Performance
factor
PLSQL_OPTIMIZE_LEVEL = 0
Yes
No
776
PLSQL_OPTIMIZE_LEVEL = 1
Yes
No
710
1.1x
PLSQL_OPTIMIZE_LEVEL = 2
Yes
Yes
456
1.7x
PLSQL_OPTIMIZE_LEVEL = 3
No
Yes
420
1.8x
[ 237 ]
[ 238 ]
Chapter 8
This approach worked well until the production DBAs showed reluctance in having
the C compiler on production environments and procuring an additional C compiler
license.
Starting from Oracle Database 11g, the Oracle Database can generate platformspecific machine code from PL/SQL code and store it in the database catalog or an
internal dictionary without the intervention of a third party C compiler. The native
code is then loaded directly from the catalog without staging it on a filesystem.
Oracle Database 11g real native compilation improves the compilation performance
by a degree of magnitude.
The following list summarizes the salient features of real native compilation process:
PLSQL_CODE_TYPE [native/interpreted].
The real native compilation mode can be set at system level, session level,
and object level. A natively compiled program unit can call an interpreted
program and vice versa.
[ 239 ]
Alternatively, you can also compile a particular PL/SQL program unit in the native
or interpreted method by recompiling an object using the ALTER <object type>
statement. For example, the following script will recompile a procedure with a new
value for PLSQL_CODE_TYPE, but reusing the existing compilation parameters:
ALTER PROCEDURE <procedure name> COMPILE PLSQL_CODE_TYPE=NATIVE REUSE
SETTINGS
/
The PL/SQL program units retain their compilation mode setting unless they are
recompiled in a different compilation mode.
[ 240 ]
Chapter 8
[ 241 ]
Chapter 8
6. You can also verify the generation of a shared library for the F_GET_CAPS
function by querying the NCOMP_DLL$ dictionary view. The library is stored
as a BLOB in the NCOMP_DLL$ view:
connect sys/system as sysdba
SELECT object_name,
dllname
FROM ncomp_dll$, dba_objects
WHERE obj#=object_id
AND owner='SCOTT'
/
OBJECT_NAME DLLNAME
------------ -------------------------------------------F_GET_CAPS
465F4745545F434150535F5F53434F54545F5F465F
5F3934303837
The library is automayically deleted from the dictionary if the object is recompiled
with a different compilation mode.
SYSDBA or a user with DBA privileges can run the recompilation procedure.
If the user is protected through the Oracle Database Vault option, the user
must be granted the DV_PATCH_ADMIN role.
You cannot exclude any PL/SQL program unit while compiling an entire
database for interpreted compilation.
[ 243 ]
The following steps show how to compile a database for native or interpreted
compilation mode:
1. Shutdown the databaseyou must shutdown the Oracle Database and the
TNS listener. Ensure that all connections to the database from the application
tier are terminated.
2. Set PLSQL_CODE_TYPE as NATIVE or INTERPRETED in the parameter file. If
you are using a server parameter file (spfile), you can modify the parameter
value before a database is shutdown, or after the database is started. The
dbmsupgnv.sql script also sets PLSQL_CODE_TYPE before compiling the PL/
SQL program units:
/*Alter the system to set the new compilation mode*/
SQL> ALTER SYSTEM SET PLSQL_CODE_TYPE=NATIVE SCOPE=SPFILE
/
System altered.
Chapter 8
##################################################################
#####
dbmsupgnv.sql completed successfully. All PL/SQL procedures,
functions, type bodies, triggers, and type bodies objects in
the
database have been invalidated and their settings set to
native.
Shut down and restart the database in normal mode and
run utlrp.sql to recompile invalid objects.
##################################################################
#####
##################################################################
#####
[ 245 ]
The script recompiles all the program units that have a default compilation
mode. You can rerun the script any number of times to recompile the
invalidated objects. The recompilation operation can be optimized through
parallelization wherein the degree of parallelism is selected based on CPU_
COUNT and PARALLEL_THREADS_PER_CPU.
For troubleshooting and diagnosis, you can use the following queries:
SELECT o.OWNER,
o.OBJECT_NAME,
o.OBJECT_TYPE
FROM DBA_OBJECTS o, DBA_PLSQL_OBJECT_SETTINGS s
WHERE o.OBJECT_NAME = s.NAME
AND o.STATUS='INVALID'
/
SELECT TYPE,
PLSQL_CODE_TYPE,
COUNT(*)
FROM DBA_PLSQL_OBJECT_SETTINGS
WHERE PLSQL_CODE_TYPE IS NOT NULL
GROUP BY TYPE, PLSQL_CODE_TYPE
ORDER BY TYPE, PLSQL_CODE_TYPE
/
[ 246 ]
Chapter 8
Query the number of the PL/SQL objects that are compiled so far
using utlrp.sql:
If you want to rollback the compilation mode to interpreted, you can follow the same
steps, and replace dbmsupgnv.sql with the dbmsupgin.sql script.
Look for a matching hash value of cached cursors in the shared pool.
[ 247 ]
If the match is not found, Oracle continues with the statement parsing and
optimization stages. This is hard parsing. It is a CPU-intensive operation and
involves contention for latches in the shared SQL area.
If the match is found, reuse the cursor and reduce parsing to a privilege
check. This is a soft parse. Thereafter, without needing further optimization,
the explain plan is retrieved from the library cache, and the SQL query
is executed.
Oracle Database 12c introduced adaptive query optimization,
which allows the optimizer to re-optimize the existing explain
plans upon subsequent executions of an SQL query.
For example, the following SELECT query will have the same hash value for the
different values of employee ids:
/*Select EMP table with a bind variable*/
SELECT ename,
deptno,
sal
FROM emp
WHERE empno = :empid
/
[ 248 ]
Chapter 8
Oracle PL/SQL supports the use of bind variables. All references to a block variable
or program argument are treated as a bind variable. In the case of a dynamic SQL,
either using DBMS_SQL or EXECUTE IMMEDIATE, you must use bind variables. Bind
variables help dynamic SQL in two ways. First it improves the code performance.
Secondly, it reduces the risk of SQL injection by covering the vulnerable areas. Let's
conduct a small illustration to see the benefits of bind variables in PL/SQL.
The following PL/SQL block finds the count of distinct object type accessible by the
SCOTT user. It executes the SELECT query using EXECUTE IMMEDIATE. In order to get
the accurate results, we will flush the shared pool:
connect sys/oracle as sysdba
ALTER SYSTEM FLUSH SHARED_POOL
/
connect scott/tiger
SET SERVEROUT ON
/*Start the PL/SQL block*/
DECLARE
/*Local block variables */
l_count NUMBER;
l_stmt VARCHAR2(4000);
clock_in NUMBER;
clock_out NUMBER;
TYPE v_obj_type is TABLE OF varchar2(100);
l_obj_type v_obj_type := v_obj_type ('TYPE', 'PACKAGE', 'PROCEDURE',
'TABLE', 'SEQUENCE', 'OPERATOR', 'SYNONYM');
BEGIN
/*Capture the start time */
clock_in := DBMS_UTILITY.GET_TIME ();
/*FOR loop to iterate the collections */
FOR I IN l_obj_type.first..l_obj_type.last
LOOP
l_stmt := 'SELECT count(*)
FROM all_objects
WHERE object_type = '||''''||l_obj_type(i)||'''';
EXECUTE IMMEDIATE l_stmt INTO l_count;
END LOOP;
/*Capture the end time */
[ 249 ]
Now, we'll rewrite the above PL/SQL block using bind variables:
connect sys/oracle as sysdba
ALTER SYSTEM FLUSH SHARED_POOL
/
connect scott/tiger
SET SERVEROUT ON
/*Start the PL/SQL block */
DECLARE
/*Local block variables */
l_count NUMBER;
l_stmt VARCHAR2(4000);
clock_in NUMBER;
clock_out NUMBER;
TYPE v_obj_type is TABLE OF varchar2(100);
l_obj_type v_obj_type := v_obj_type ('TYPE', 'PACKAGE', 'PROCEDURE',
'TABLE', 'SEQUENCE', 'OPERATOR', 'SYNONYM');
BEGIN
/*Capture the start time */
clock_in := DBMS_UTILITY.GET_TIME ();
/*FOR loop to iterate the collection */
FOR I IN l_obj_type.first..l_obj_type.last
LOOP
/*Build the SELECT statement by using bind variable*/
l_stmt := 'SELECT count(*)
FROM all_objects
WHERE object_type = :p1';
/*Use dynamic SQL to execute the SELECT statement*/
EXECUTE IMMEDIATE l_stmt INTO l_count USING l_obj_type(i);
[ 250 ]
Chapter 8
END LOOP;
clock_out := DBMS_UTILITY.GET_TIME ();
DBMS_OUTPUT.PUT_LINE ('Execution time with bind variables:'||TO_
CHAR(clock_out-clock_in));
END;
/
Execution time with bind variables:121
PL/SQL procedure successfully completed.
The block with the bind variables gets executed at least 8 times faster than the one
which uses literals in the SQL query inside the PL/SQL block. The reason for the
performance gain is the soft parsing of the SELECT statement.
In the case of legacy applications or access protected applications, you might face
difficulties in modifying the code to include the bind variables. Oracle makes this
daunting task easier by controlling the cursor sharing behavior through a switch.
You can set CURSOR_SHARING parameter to EXACT or FORCE to share the cursors across
the sessions in a database instance. If the parameter is set to EXACT, only the SQL
statements that have exactly the same structure and parameters will be shared. If the
parameter is set to FORCE, then Oracle attempts to substitute all the literals in a query
with system-generated bind variables. By default, the parameter value is set to EXACT.
Cursor sharing greatly improves the performance. At the same time, forced cursor
sharing involves extra effort in searching for the same cursor in the shared pool.
The SIMILAR value of CURSOR_SHARING has been
deprecated in Oracle Database 12c.
Oracle recommends that you specify the NOCOPY hint for the pass by value
parameters. A parameter with the pass mode as OUT NOCOPY or IN OUT NOCOPY is
passed by reference, thus avoiding the overhead of maintaining a temporary variable.
For simple data values, which are generally small, the gain will be negligible.
Understand that SQL data types and PL/SQL data types have different internal
representations. You should explicitly convert the SQL type variables to PL/
SQL types and then use those in an expression. For example, PLS_INTEGER is a
PL/SQL data type that uses machine arithmetic to speed up compute intensive
operations. If NUMBER type variables are used in an expression, Oracle uses
library arithmetic, and therefore, there are no performance gains.
Use the SQL conversion function to convert the to-be assigned variable to the
target variable's data type. Some of the conversion functions are TO_CHAR,
TO_NUMBER, TO_DATE, TO_TIMESTAMP, CAST, and ASCIISTR.
[ 252 ]
Chapter 8
Pass a variable of correct data type while invoking the PL/SQL program
units with parameters. You can also include overloaded subprograms in a
PL/SQL package that accepts the parameters of the different data types. You
can invoke the packaged subprogram that best matches the available set of
variables. For example, the following package has two overloaded functions.
You can invoke either of the two depending on the type of variables available:
CREATE OR REPLACE PACKAGE pkg_sum AS
FUNCTION p_sum_series (p_term PLS_INTEGER, p_factor PLS_INTEGER)
RETURN PLS_INTEGER;
FUNCTION p_sum_series (p_term NUMBER, p_factor NUMBER)
RETURN NUMBER;
END;
/
PL/SQL block*/
NUMBER NOT NULL := 0;
NUMBER := 0;
NUMBER;
NUMBER;
[ 253 ]
In the preceding PL/SQL block, the nullable variable outperforms the NOT NULL
variable by one and a half times. The reason for this performance gain is the
reduction in the number of nullability checks.
Chapter 8
The following PL/SQL block compares the performance of the PLS_INTEGER and
NUMBER variables:
/*Enable the SERVEROUTPUT to display block results*/
SET SERVEROUTPUT ON
/*Start the
DECLARE
l_pls_int
l_num
l_factor
clock_in
clock_out
BEGIN
PL/SQL block*/
PLS_INTEGER := 1;
NUMBER:= 1;
PLS_INTEGER := 2;
NUMBER;
NUMBER;
The performance of the PLS_INTEGER variable is at least three times better than a
NUMBER variable. An arithmetic expression having a mix of the PLS_INTEGER and
NUMBER type variables will use library arithmetic and no performance gains will
be obtained.
PLS_INTEGER is a 32-bit data type that can store values in the range of -2147483648
to 2147483647. It accepts integer values only. If a floating value is assigned to
PLS_INTEGER, it is rounded to the nearest integer. If the PLS_INTEGER precision
range is violated, Oracle raises an ORA-01426: numeric overflow exception. To
resolve such scenarios, Oracle 11g introduced a new subtype of PLS_INTEGER, which
is known as SIMPLE_INTEGER. The SIMPLE_INTEGER data type has the same range as
that of PLS_INTEGER, but in the case of numeric overflows, it is automatically set to
-2147483648 instead of raising an exception. As the overflow check is suppressed
for SIMPLE_INTEGER, it is faster than PLS_INTEGER.
SIMPLE_INTEGER is a NOT NULL data type; therefore, all local
variables must be initialized or defaulted to a definitive value.
Chapter 8
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Numeric Overflow exception occurred');
END;
/
After 1st increment:2147483647
After 2nd increment:-2147483648
PL/SQL procedure successfully completed.
BULK COLLECT
You can use BULK COLLECT in:
The bulk collect feature depends largely on collections and can be used with all
the three forms of collections, that is, associative arrays, nested tables, and varrays.
Multiple records can be fetched into a collection in a single fetch. This reduces the
number of the context switches between the two processing engines. During the
fetch operation, the collection variables get densely populated. In the case of no rows
being fetched, the collections are left empty resulting in an empty collection.
[ 257 ]
Until Oracle Database 9i, BULK COLLECT could be used only with static SQL
statements, but starting with Oracle Database 10g, it can be used in dynamic SQL
statements too.
The CREATE TABLE script creates the test table with the sample data from the
ALL_OBJECTS dictionary view to be used in this illustration. The ALL_OBJECTS
dictionary view contains details of the schema objects. During our illustration, we
will work with the object id, object type, and object name columns of the table:
CREATE TABLE local_objects AS
SELECT * FROM all_objects
/
/*Query the record count*/
SELECT COUNT(*)
FROM local_objects
/
COUNT(*)
---------73673
The following PL/SQL block opens a cursor, fetches row by row, and counts the
number of procedures that can be accessed by the SCOTT user:
SET SERVEROUTPUT ON
/*Start the PL/SQL block*/
DECLARE
/*Local PL/SQL variables*/
obj_id
local_objects.object_id%TYPE;
obj_type local_objects.object_type%TYPE;
obj_name local_objects.object_name%TYPE;
counter
NUMBER;
clock_in NUMBER;
clock_out NUMBER;
/*Cursor to fetch the object details*/
CURSOR c IS
SELECT object_id, object_type, object_name
[ 258 ]
Chapter 8
FROM local_objects;
BEGIN
/*Capture the start time*/
clock_in := DBMS_UTILITY.GET_TIME();
OPEN c;
LOOP
FETCH c INTO obj_id, obj_type, obj_name;
EXIT WHEN c%NOTFOUND;
/*Count the number of procedures in the test table*/
IF obj_type = 'PROCEDURE' THEN
counter := counter+1;
END IF;
END LOOP;
CLOSE c;
/*Capture the end time*/
clock_out := DBMS_UTILITY.GET_TIME();
DBMS_OUTPUT.PUT_LINE ('Time taken in row fetch:'||to_char (clock_
out-clock_in));
END;
/
Time taken in row fetch:369
PL/SQL procedure successfully completed.
The row-by-row fetch took 369 hsec to get executed. There were 73673 context
switches made between the PL/SQL and SQL engines. Now, let's apply the bulk
fetch techniques to the preceding block, and check the performance gains:
SET SERVEROUTPUT ON
/*Start the PL/SQL block*/
DECLARE
/*Declare the local record and table collection*/
TYPE obj_rec IS RECORD
(obj_id local_objects.object_id%TYPE,
obj_type local_objects.object_TYPE%TYPE,
obj_name local_objects.object_name%TYPE);
TYPE obj_tab IS TABLE OF obj_rec;
t_all_objs obj_tab;
[ 259 ]
Well, the bulk SQL is around 10 times faster than the usual fetch operation. The
reason is that there was only one context switch made between the SQL and PL/SQL
engines. Note that the nested table collection variable is not required to be initialized
for bulk operations.
The BULK COLLECT operation does not raise the NO_DATA_FOUND
exception.
[ 260 ]
Chapter 8
A controlled bulk operation, limited to 100 records per bulk fetch, took 94 hsec to
get executed, which is still 4 times better than the row-by-row fetch operation. This
time the program makes 737 context switches between the PL/SQL and SQL engines.
FORALL
More than iterative, FORALL is a declarative statement. The DML statements
specified in FORALL are generated once, but they send in bulk to the SQL engine for
processing, thus making only a single context switch. There can be only one DML
statement in FORALL that can be INSERT, UPDATE, or DELETE, or the FORALL statement
can be used, as per the following syntax:
FORALL index IN
[
lower_bound...upper_bound |
INDICES OF indexing_collection |
VALUES OF indexing_collection
]
[SAVE EXCEPTIONS]
[DML statement]
If the DML statements contain bind variables, the bulk SQL binds all the statements
in a single step. This is known as bulk binding.
[ 262 ]
Chapter 8
Let's set up the environment for our performance test. The performance test will
compare the execution time of a FOR loop versus a FORALL statement:
/*Create table T1 with basic object columns*/
CREATE TABLE T1
AS
SELECT object_id, object_type, object_name
FROM all_objects
WHERE 1=2
/
/*Create table T2 with basic object columns*/
CREATE TABLE T2
AS
SELECT object_id, object_type, object_name
FROM all_objects
WHERE 1=2
/
The following PL/SQL block bulk collects the data from the LOCAL_OBJECTS table,
creates an interim collection (associative array) for only the synonym information,
and inserts it into two different tables. For verification, we will query the records
inserted in both the tables:
SET SERVEROUTPUT ON
/*Start the PL/SQL block*/
DECLARE
/*Local block variables - object type record and nested table
collection*/
TYPE obj_rec IS RECORD
(obj_id local_objects.object_id%TYPE,
obj_type local_objects.object_TYPE%TYPE,
obj_name local_objects.object_name%TYPE);
TYPE obj_tab IS TABLE OF obj_rec;
TYPE syn_tab IS TABLE OF obj_rec index by pls_integer;
t_all_objs obj_tab;
t_all_syn syn_tab;
counter NUMBER := 1;
clock_in NUMBER;
clock_out NUMBER;
BEGIN
[ 263 ]
[ 264 ]
Chapter 8
/*Query the record count in T2*/
SQL> SELECT COUNT(*) FROM t2
/
COUNT(*)
---------37031
/*Query the record count in T1*/
SQL> SELECT COUNT(*) FROM t1
/
COUNT(*)
---------37031
This is phenomenal! The FORALL statement inserted more than 37000 records in less
than a second, whereas the FOR LOOP statement took 16 sec to insert the same number
of records. The reason is again the context switch between the processing engines;
one switch against 37000 context switches.
In the case of a sparse collection, use INDICES OF or VALUES OF in the FORALL
statement. It will use only those indexes of the collection that hold a value. The
following PL/SQL block uses the FORALL statement to insert a sparse collection into
the T2 table. To sparse the interim collection, the synonyms starting with SYS% are
deleted from it:
/*Truncate table T2 for the current test*/
TRUNCATE TABLE t2
/
SET SERVEROUTPUT ON
/*Start the PL/SQL block*/
DECLARE
/*Local block variables - object type record and nested table
collection*/
TYPE obj_rec IS RECORD
(obj_id local_objects.object_id%TYPE,
obj_type local_objects.object_TYPE%TYPE,
obj_name local_objects.object_name%TYPE);
TYPE obj_tab IS TABLE OF obj_rec;
TYPE syn_tab IS TABLE OF obj_rec index by pls_integer;
t_all_objs obj_tab;
[ 265 ]
[ 266 ]
Chapter 8
Time taken by FORALL:30
PL/SQL procedure successfully completed.
/*Query the record count in table T2*/
SQL> select count(*) from t2;
COUNT(*)
---------37025
There were six synonyms starting with SYS% that were not inserted in this run.
If you hadn't used the INDICES OF clause, the block would have terminated with the
ORA-22160: element at index [3] does not exist exception.
1. Abort the FORALL execution but commit the changes made by the earlier
transactions: Traditional exception handling but with a COMMIT in the
exception handler. For example, the following exception handle will commit
the transactions that have been executed already:
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE (SQLERRM);
COMMIT;
RAISE;
2. Continue the FORALL execution and save the failed transactions: Use SAVE
EXCEPTIONS with the FORALL statement. The feature is known as bulk
exception handling. With SAVE EXCEPTION, Oracle stores the faulty DML
details in the bulk exception logger known as SQL%BULK_EXCEPTIONS. After
the FORALL execution is over, database administrators can look into the
exception log and troubleshoot the defective records. The defective records
are skipped and logged under the SQL%BULK_EXCEPTIONS pseudocolumn.
For example, if FORALL generated 5000 update statements, out of which 13
were failed transactions, 4987 records will still be updated. The 13 defective
transactions will be logged in the SQL%BULK_EXCEPTIONS array structure
with the cursor index.
[ 267 ]
Now, we will run the following PL/SQL block to insert the dense collection into the
T2 table. As SAVE EXCEPTIONS has been specified along with the FORALL statement, if
there are any exceptions while loading, they will be saved in the bulk exception log:
SET SERVEROUTPUT ON
/*Start the PL/SQL block*/
DECLARE
/*Local block variables - object type record and nested table
collection*/
TYPE obj_rec IS RECORD
(obj_id local_objects.object_id%TYPE,
obj_type local_objects.object_TYPE%TYPE,
obj_name local_objects.object_name%TYPE);
TYPE obj_tab IS TABLE OF obj_rec;
TYPE syn_tab IS TABLE OF obj_rec index by pls_integer;
t_all_objs obj_tab;
t_all_syn syn_tab ;
counter NUMBER := 1;
clock_in NUMBER;
clock_out NUMBER;
[ 268 ]
Chapter 8
[ 269 ]
(.) violated
(.) violated
(.) violated
(.) violated
(.) violated
(.) violated
The transactions that failed to get executed due to the check constraint violation were
saved in the BULK_EXCEPTIONS cursor attribute. Besides these six records, all the
other data was inserted, and this can be confirmed by a SELECT query on the table.
Summary
In this chapter, we have discussed the features that can tune your PL/SQL code to
run faster. We have also seen how code compilation and optimization can speed up
the performance of the PL/SQL programs. In the later half, we discussed the
PL/SQL tuning features in detail with the help of demonstrations.
In the next chapter, we will focus on one of the major new features introduced in
Oracle Database 11g. The feature is known as result caching that is primarily built to
accelerate the performance of queries that are repeatedly executed. We will discuss
the different flavors of result caching in the next chapter.
[ 270 ]
Chapter 8
Practice exercise
Identify the nature of the program that is best suited for the interpreted mode
of compilation.
1. The program unit contains multiple SQL statements.
2. The program unit has just been developed and is in the debug stage.
3. The program unit uses collections and bulk bind statements.
4. The program unit is in the production phase.
Choose the correct statements about the real native compilation mode in
Oracle 11g.
1. The compilation method uses the C compiler to convert the program
into an equivalent C code.
2. The compilation method mounts the shared libraries through the
PLSQL_NATIVE_LIBRARY_DIR and PLSQL_NATIVE_LIBRARY_SUBDIR_
COUNT parameters.
3. The compilation does not use the C compiler but converts the
program unit directly to the M code.
4. The real native compilation is supported for RAC environments and
participates in the backup recovery processes.
[ 271 ]
Chapter 8
L_SUM := L_SUM + F_ADD (I);
END LOOP;
END;
/
1. PLSQL_OPTIMIZE_LEVEL is set to 2.
2. The F_ADD local function would not be called inline unless PLSQL_
OPTIMIZE_LEVEL is set to 3.
3. The F_ADD local function may be called inline because PLSQL_
OPTIMIZE_LEVEL is set to 2.
4. The F_ADD local function would be called inline because PRAGMA
INLINE marks it for inline.
5. Inlining cannot be done for locally declared subprograms.
The libraries generated from real native compilation are stored in the SYSAUX
tablespace.
1. True
2. False
[ 273 ]
[ 274 ]
www.PacktPub.com
Stay Connected: