You are on page 1of 26

Techniques For Tuning PL/SQL Applications


An Oracle Technical White Paper
March 1999
INTRODUCTION

Oracle8i PL/SQL™ provides seamless and tight integration with SQL and the Oracle server. Oracle
PL/SQL is widely used for building scalable, portable, and robust enterprise applications. As
organizations grow, there is a need to make the applications that service them more scalable and
faster. Moreover, due to the explosive growth of the amount of available data, performance has now
become a critical issue, even for customers who have not modified their existing applications.
Oracle8i PL/SQL has several new features that improve application performance and increase
scalability, by optimizing memory usage. Some of these features are transparent to the end user and,
with simple recompilation, will improve the performance of existing applications. Other features
require the use of new syntax and, therefore, the modification of existing applications, to exploit the
benefits that the new features offer.

To improve application performance and scalability, the following sequence of steps needs
to be executed:

•= Evaluate performance of existing applications, and identify bottlenecks/areas for improvement.

•= Analyze the characteristics of the application code that needs improvement, and identify the
relevant features that can be used for improving performance.

•= Develop/modify the application so that they can use features and performance tips to extract the
maximum performance.

This paper describes the PL/SQL tools and features for implementing the above methodology, in the
following order:

•= A discussion of the use of profiling tools for benchmarking existing applications and identifying
areas for improving performance.

•= A description of the features that can significantly improve performance. In addition to


performance improvements, this paper also discusses the memory usage improvements of PL/SQL
programs. Significant improvements have been made to reduce the session memory (also called,
UGA — user global memory) that PL/SQL requires, resulting in better PL/SQL
application scalability.

•= Some PL/SQL performance tips that can be used for improving application performance
and scalability.

Techniques For Tuning PL/SQL Applications 2


March 1999
APPLICATION ANALYSIS

As mentioned in the introduction, the first step in improving an application's performance is to


identify the characteristics of the application and the bottlenecks in the code. This will allow the
developer to determine the portions of the application where the maximum time is spent, which, in
turn, allows attention to be focused on the relevant program segments and data structures. The
Profiling tool described below can be used to analyze PL/SQL applications.

PROFILING

As Oracle8i PL/SQL users develop increasingly large numbers of PL/SQL packages that are used as
components, the need to identify and isolate performance problems becomes critical. Oracle8i
provides a profiler, with which customers can:

•= Profile their existing PL/SQL applications.

•= Locate bottlenecks.

•= Focus on improving the performance of any bottlenecks found.

Using the profiling tool, application developers can generate profiling information for all named
library units used in a single session. The profiling information is stored in a database, to help
generate information on time spent in each PL/SQL line, module, and routine. A sample textual
report writer is provided (refer to [7] for more details). The report writer can extract the persistent
profiling information that the profiler stores, to identify the time spent at each line. Thus, it allows
application developers to identify the program segments that need improvement.

Once the program segments that require improvements have been identified, the next step is to
analyze why time is spent in certain code segments. The feedback from the profiler can be used to re-
work the algorithms (or perhaps select new algorithms) used in the application. In addition, the
analysis phase can point out inefficiencies caused by the choice of inappropriate data structures. This
information must be considered together with the new features that PL/SQL provides, to identify
whether more performance can be extracted from the applications. To conclude, application analysis
is an important phase that drives the rest of the application tuning effort that developers undertake.

Techniques For Tuning PL/SQL Applications 3


March 1999
PL/SQL FEATURES FOR IMPROVING PERFORMANCE

Oracle8 PL/SQL Release 8.0 and Oracle8i PL/SQL Release 8.1 support a variety of new features that
can be used to develop more efficient applications. The following sections describe the new features
that have been added to the language. In addition, Oracle devoted a lot of effort towards
transparently improving the performance of the existing Oracle PL/SQL applications.

NEW FEATURES IN ORACLE8I PL/SQL

As summarized in the table below, several new capabilities have been added in Oracle8i PL/SQL in
the areas of performance.

PROGRAM IMPROVEMENT FEATURE


CHARACTERISTICS

Applications that execute SQL Faster data transfer between Bulk Binds
statements in a loop PL/SQL and SQL

Applications that contain Faster parameter passing mode NOCOPY


functions/procedures that pass
large records, ADTs, or
collections as parameters

Applications that use the Faster support for dynamic SQL Native dynamic SQL
DBMS_SQL package statements

Applications that implement Efficient and convenient External Procedures


computational business logic alternative to implement
with few database (SQL) compute-intensive code
operations

The following sections describe these features in more detail.

Techniques For Tuning PL/SQL Applications 4


March 1999
Bulk Binds: Faster Data Transfer Between PL/SQL and SQL

The PL/SQL interpreter executes all the procedural statements, but sends the SQL statements to the
SQL engine, which parses and executes the SQL statements and, in some cases, returns data to the
PL/SQL engine. Although this execution path has been heavily optimized, to support faster
execution of the embedded SQL statements, the context switch adds some overhead. The
performance penalty can become noticeable when SQL statements are nested in loops, as
demonstrated in the following example:

-- Assume that orderId, orderDate, etc. (one for each column


of the

-- table Orders) have been filled appropriately with all the


new

-- Orders that need to be inserted into the database. Assume


that

-- there are 1000 orders.

FOR i In 1..1000

LOOP

INSERT INTO Orders VALUES (orderId(i), orderDate(i), ...);

END LOOP;

The overhead can be reduced, by minimizing the number of context switches. The bulk binds
features allow users to decrease the overhead, by operating on multiple rows in a single DML
statement. With bulk binds, entire collections, not just the individual collection elements, are
passed back and forth between PL/SQL and SQL engines. Oracle8i PL/SQL supports new
keywords — FORALL and BULK COLLECT — to support bulk binds. The above example may be
transformed as follows, to use bulk binds:

Techniques For Tuning PL/SQL Applications 5


March 1999
FORALL i In 1..1000

INSERT INTO Orders VALUES (orderId(i), orderDate(i), ...);

For more details regarding this feature, please refer to [2].

NOCOPY: Faster Parameter Passing

PL/SQL supports three parameter passing modes:

•= IN parameters are passed by reference.

•= IN OUT parameters are implemented as copy-in/copy-out.

•= OUT parameters are implemented as copy-out.

This copying protects the original values of the arguments, if exceptions are raised in the called
function/procedure. However, such copying imposes CPU and memory overhead, especially when
the parameters involved are large data structures, such as large strings or collections.

In Oracle8i, PL/SQL supports a new "NOCOPY" modifier that can be used to avoid this overhead, by
allowing IN OUT and OUT parameters to be passed efficiently by reference, when possible. Use of
this new modifier will result in performance improvements for any application that passes large data
structures as IN OUT or OUT parameters. The following example shows the use of the
NOCOPY modifier.

-- The IN OUT parameter "word_list" in "add_entry" is passed


by

-- reference when possible

TYPE Definition IS RECORD (

word VARCHAR2(20),

meaning VARCHAR(200)

Techniques For Tuning PL/SQL Applications 6


March 1999
);

TYPE Dictionary IS VARRAY(2000) OF Definition;

CREATE PROCEDURE add_entry(word_list IN OUT NOCOPY Dictionary)


IS

BEGIN

...

END;

Similarly, OUT parameters can be passed by reference. Note that the NOCOPY modifier cannot be
combined with the IN parameter mode, because PL/SQL passes all IN parameters by reference. For
more details, please refer to [4].

NOCOPY parameter mode will allow users to develop programs with better program abstraction. In
the absence of the NOCOPY mode, the developers were forced to declare large collection/records as
package global variables. The NOCOPY mode eliminates the need for such workarounds. Using the
NOCOPY mode for IN OUT or OUT parameters can yield performance improvements in the
range of 30%.

Native Dynamic SQL: Faster Execution of Dynamic SQL Statements

Native Dynamic SQL in PL/SQL provides the ability to dynamically execute SQL statements whose
complete text is not known until execution time (runtime). These dynamic statements could be data
manipulation language (DML) statements (including queries), PL/SQL anonymous blocks, data
definition language (DDL) statements, transaction control statements, session control statements
(with possible restrictions), etc.

Native Dynamic SQL can be used instead of the DBMS_SQL package to improve the performance of
PL/SQL programs. The PL/SQL interpreter has been extended to provide native support for native
dynamic SQL. Thus, programs with native dynamic SQL perform much better than ones with the

Techniques For Tuning PL/SQL Applications 7


March 1999
DBMS_SQL package. In addition, the DBMS_SQL approach is based on a procedural API and, as a
result, suffers from high procedure call and data copy overhead. We have seen significant
performance improvements (up to 200%) over DBMS_SQL in our small benchmarks.

For example, consider the following program segment that demonstrates the use of DBMS_SQL. It
also illustrates the use of native dynamic SQL. Please refer to [3] for more details.

DML example, using the Equivalent DML example with


DBMS_SQL package Native Dynamic SQL

PROCEDURE insert_into_table PROCEDURE insert_into_table


(table_name VARCHAR2, (table_name VARCHAR2,

deptnumber NUMBER, deptnumber NUMBER,


deptname VARCHAR2, deptname VARCHAR2,

location VARCHAR2) location VARCHAR2)

IS IS

cur_hdl INTEGER; stmt_str VARCHAR2(200);

stmt_str BEGIN
VARCHAR2(200);
stmt_str := 'insert into '
rows_processed || table_name || ' values
BINARY_INTEGER; (:deptno,

BEGIN :dname,
:loc)';
stmt_str := 'insert into
' || table_name || ' values -- bundled execution using
(:deptno, native dynamic SQL

EXECUTE IMMEDIATE stmt_str


:dname, :loc)';
USING
-- open cursor deptnumber, deptname,
location;
cur_hdl :=
dbms_sql.open_cursor; END insert_into_table;

Techniques For Tuning PL/SQL Applications 8


March 1999
-- parse cursor

dbms_sql.parse(cur_hdl,
stmt_str, dbms_sql.native);

-- supply binds

dbms_sql.bind_variable(cur_hd
l, ':deptno', deptnumber);

dbms_sql.bind_variable(cur_hd
l, ':dname', deptname);

dbms_sql.bind_variable(cur_hd
l, ':loc', location);

-- execute cursor

rows_processed :=
dbms_sql.execute(cur_hdl);

-- close cursor

dbms_sql.close_cursor(cur_hdl
);

END insert_into_table;

On every bind, the DBMS_SQL package implementation makes copies of the PL/SQL bind variable
into its space, for use during the execution phase. Similarly, on every fetch, the data is first copied
into the space that the DBMS_SQL package manages. Therefore, the fetched data is copied, one
column at a time, into the appropriate PL/SQL variables, resulting in a significant data
copying overhead.

Techniques For Tuning PL/SQL Applications 9


March 1999
External Procedures: Faster Execution of Compute-Intensive Logic

Oracle8 PL/SQL Release 8.0 provided support for external procedures, which allow a simple, easy,
and safe way to interface external systems and 3GL application code with the database server, while
preserving transactional semantics. They can be called from a number of different contexts,
including from SQL, PL/SQL stored procedures, functions and triggers, client OCI, and
Precompiler programs.

Several optimizations were done in Oracle8i PL/SQL Release 8.1 that decrease the overhead
associated with callouts. Customers migrating from Oracle8 to Oracle8i can take advantage of the
performance benefit, by merely recompiling their applications.

PL/SQL is closely tied to the Oracle Server, for SQL transaction processing. Complex number
crunching can be very expensive in PL/SQL and is best done efficiently, in lower level languages like
C. Consider the following CPU-intensive PL/SQL code fragment for computing the
Fibonacci numbers:

CREATE FUNCTION fib(n PLS_INTEGER) RETURN PLS_INTEGER IS

BEGIN

IF (n < 2)

RETURN n;

ELSE

RETURN (fib(n-2) + fib(n-1));

END IF;

END;

And the C fragment:

Techniques For Tuning PL/SQL Applications 10


March 1999
unsigned int fib(n)

unsigned int n;

if (n < 2)

return n;

else

return (fib(n-2) + fib(n-1));

Our experiments have shown that C code performs better than PL/SQL, even for values as
small as N=10.

TRANSPARENT PL/SQL PERFORMANCE IMPROVEMENTS

The Oracle8i PL/SQL Release 8.1 runtime engine has been further tuned to allow existing
applications to run faster transparently. The following table highlights these as well as
improvements made in Oracle8 PL/SQL Release 8.0:

Techniques For Tuning PL/SQL Applications 11


March 1999
Program Characteristics Feature Release

Applications that use the standard builtins Optimization of Package STANDARD 8.1
provided by PL/SQL Builtins

OCI, SQL*Plus, DBMS_SQL, etc. Faster Anonymous Block Execution 8.1


applications that invoke PL/SQL stored
procedures, by building anonymous blocks

Applications that make RPC calls (client- RPC Parameter Passing Improvements 8.1
to-server or server-to-server) and pass large
records, ADTs, and collections

Application uses triggers, and calls PL/SQL Faster Calling PL/SQL from SQL Context 8.0
functions from SQL context

All applications Code-generation optimizations to minimize 8.0


temporaries

Applications that use records Efficient Implementation of PL/SQL 8.0


Records

Applications that use collections Improved PL/SQL Index-By Table 8.0


Implementation

Optimization of Package STANDARD Built-Ins

Calls to package Standard built-ins (for example, TO_CHAR, TO_DATE, SUBSTR, etc.) were
improved in Oracle8i PL/SQL, by optimizing the code path to call such built-ins, resulting in faster
execution. This is a huge transparent improvement, because most applications are heavy users of
these built-ins. Our benchmark runs have shown performance gains of 10-30%. The performance
gains become more significant if built-ins are used more heavily in the application.

Techniques For Tuning PL/SQL Applications 12


March 1999
Faster Anonymous Block Execution

The following improvements were made in Oracle8i PL/SQL Release 8.1:

•= The invocation of anonymous blocks with bind variables has doubled in speed, due to elimination of
considerable code path in bind variable processing.

•= Accessing elements of host array bind variables is significantly faster in Oracle8i PL/SQL.

•= We now optimize host binds that are used only in SQL statements, in anonymous PL/SQL blocks,
by avoiding unnecessary temporaries.

•= Redundant rebinds for host bind variables to SQL statements in an anonymous block have been
eliminated for repeated executions of the anonymous block.

In addition, and perhaps more significantly, calling PL/SQL functions and triggers from SQL will
now be much faster, because this is internally implemented, using anonymous blocks with
bind variables.

RPC Parameter Passing Improvement

The overhead associated with RPC parameters has been reduced in Oracle8i PL/SQL Release 8.1, by
eliminating some redundant temporaries. Any call that passes large records, ADTs, or collections
(including index-by tables) will benefit from this optimization. This optimization is applicable to
the server-to-server, as well as client-server RPC calls.

Faster Calling PL/SQL From SQL Context

The overhead of calling PL/SQL functions (or triggers) from SQL context has been made negligible in
Oracle8 PL/SQL Release 8.0. Several optimizations were done in this area. Anonymous blocks built
for invoking PL/SQL from SQL are now compiled and kept as part of the parent SQL cursor. A
critical latching bottleneck, which made PL/SQL invocation in Parallel Query operations very
expensive, has been eliminated. Some structures (for example, date context) are now cached in the
PL/SQL interpreter context. As a result, repeated invocations of PL/SQL (for example, on every row
of a SQL query) have been made more efficient. Invocation of anonymous blocks with bind variables
is much faster (as described in the previous section).

Techniques For Tuning PL/SQL Applications 13


March 1999
Code Generation of Optimizations to Minimize Use of Temporaries (Oracle8
Improvement)

Code generation optimizations were done to minimize the use of temporaries during expression
evaluation, thereby resulting in smaller and more efficient MCODE. For example, consider the
following PL/SQL statement:

str1 := str2 || str3;

In PL/SQL Releases 2.x, the result of the expression, "str2 || str3," would be computed into a
temporary variable, and then the temporary would be copied to the variable "str1," with appropriate
constraint checking. From PL/SQL Release 8.0 on, a single byte-code instruction is generated to
compute and store the result of the concatenation directly into "str1."

This significantly improves execution time, by reducing the amount of data copy and the time spent
in creation of temporaries. This temporary elimination optimization also applies to assignment
statements involving implicit conversions, such as:

number_variable := pls_integer_variable;

number_variable := char_variable;

For the above statements, temporaries are no longer generated to hold the result of the type
conversion. The left-hand side variable is directly used as the target of the conversion.

Efficient Implementation of PL/SQL Records

In PL/SQL Release 2.3.4 and earlier releases, there was no true support for RECORDs in the run-
time. The compiler exploded a RECORD into its individual scalar fields, which resulted in
inefficient code. Starting with Release 8.0, the PL/SQL run-time provides native support for
composite types, such as RECORDs and OBJECTs. The run-time uses structural type descriptors,
generated during compilation, to implement operations (such as "copy," "RPC argument
marshaling,", etc.) on data items of these types.

Techniques For Tuning PL/SQL Applications 14


March 1999
Improved PL/SQL Index-By Table Implementation

Collection data types (index-by tables, nested tables, and varrays) have been implemented using a
new paged-array representation. This replaces the previous B-tree-based implementation for index-
by tables, which had the disadvantage that there could be a lot of data movement during insert
operations, due to tree balancing.

Operations such as lookup, insert, and copy should be faster now. For dense collections, especially,
the paged-array scheme exhibits much better memory utilization characteristics than the previous B-
tree scheme.

Other Enhancements

A number of other enhancements have been added that make PL/SQL execution efficient. This list
includes faster implementations for:

•= Byte-code operand fetches.

•= Scope (procedure frame) entry and exit (by speeding up interpreter's register save/restore operations).

•= Common built-in operations, like "to_char" on numeric types.

•= Access to a package's own global variables.

•= Bind variable reads/writes.

•= Dynamic SQL that uses DBMS_SQL.

•= Assignments to numbers without scale and precision constraints.

•= Null initialization of frame variables on entry to scope.

•= Runtime code used for executing the RETURNING INTO clause of SQL DML statements was
tuned to reduce overhead.

TRANSPARENT MEMORY USAGE IMPROVEMENTS

The scalability of large applications directly depends on the memory requirement of the application
code. In addition to the performance improvements, the memory management in Oracle8 has been
tuned to reduce the memory usage of the applications. The size of PL/SQL MCODE (compiled byte-

Techniques For Tuning PL/SQL Applications 15


March 1999
code form), which is loaded in system global memory (SGA), is down about 20-25%, compared to
Oracle7. Major improvements have been made in reducing user global memory (UGA) that PL/SQL
packages performing SQL operations consume.

For applications such as Oracle Office, the UGA consumption due to PL/SQL packages is down about
40%. These improvements have played a crucial role in enabling Oracle8 applications to scale to
tens of thousands of users (see [1]). The key improvements are summarized in the table below:

Program Characteristics Feature Release

All applications Reduction in Shared Pool (SGA) 2.3, 8.0


fragmentation

All applications Reduction in SGA utilization 8.0

Applications with SQL statements Reduction in UGA, due to 8.0


Improvements in Embedded SQL
Execution

Applications that have variables of Dynamic Allocation of Large 8.0


VARCHAR2 and RAW datatypes VARCHAR2 and RAW variables

Applications that don’t need to maintain Serially Reusable Packages 8.0


state across calls to the server

The following sections describe these improvements in more detail.

Reduction in Shared Pool (SGA) Fragmentation

On the server, the SGA is implemented using shared memory — a range of virtual addresses is
mapped to the shared segment on every Oracle process. As an Oracle instance continues to service
requests, the free SGA memory tends to become fragmented, thus making it harder to satisfy
requests for large chunks.

Execution of a PL/SQL library unit involves loading its MCODE into SGA memory. Two major sub-
pieces of the MCODE are the code segment and constant pool. The code segment contains the

Techniques For Tuning PL/SQL Applications 16


March 1999
byte-code (or the instruction) sequence for the program. The constant pool piece holds the literal
constants in the program, as well as various other descriptors that the interpreter uses to perform
tasks, like PL/SQL RPC, exception processing, SQL processing, and so on.

Until PL/SQL V2.2, the code segment and the constant pool pieces were allocated as contiguous
chunks in the SGA. In PL/SQL V2.3, code segment paging was implemented, which helped alleviate
some of the SGA fragmentation concerns. In PL/SQL V8, the constant pool portion of the PL/SQL
MCODE is also paged.

Virtual machines that implement paging completely at load time suffer from the problem that
instructions or literal data can span page boundaries, and, hence, such VMs need to check for page
faults during instruction or literal data fetches. This can result in poor performance. In contrast, one
of the salient characteristics of PL/SQL paging is that parts of the paging logic have been designed
right into the compiler, which guarantees that no instruction or literal data item will span page
boundaries. This characteristic helps minimize the number of page table lookups, and also
eliminates the need to do any segmented fetches at run-time.

Reduction in SGA Utilization

The SGA memory consumption of PL/SQL libraries has been considerably reduced. The MCODE
size of a PL/SQL library unit in V8 is about 20-25% less than in V7. Some of the optimizations done
to achieve this were:

•= The use of a compression scheme in the byte-code operand specification.

•= Initialization of variables upon entry into a scope that uses a compact descriptor, which lives in the
constant pool part of the MCODE, instead of using individual byte-code instructions.

Reduction in UGA Due to Improvements in Embedded SQL Execution

In V8 there has been a substantial improvement in the execution model for SQL statements
embedded in PL/SQL, which resulted in substantial UGA memory savings and performance
improvements. The UGA memory consumption of PL/SQL packages is down about 40% compared
to Oracle7, due to these changes. Previously, for SQL executed from PL/SQL, a contiguous buffer
would be allocated in UGA to hold the values of the input and output variables for the SQL

Techniques For Tuning PL/SQL Applications 17


March 1999
statement. An extra copy step was needed to move data between this buffer and the actual PL/SQL
variables in the SQL statement. Although this buffer would be reused if the SQL statement was to be
executed again, the main disadvantage is that it remains allocated in the UGA till the end
of the session.

In V8, these input/output buffers have been eliminated. Instead, the binds and defines to the SQL
statement are done directly, using the PL/SQL variables, thus saving both time spent on the extra
copy step and UGA memory.

Dynamic Allocation of Large VARCHAR2 and RAW Variables

In Oracle8 PL/SQL, variables of type VARCHAR2 and RAW are dynamically allocated and resized
as appropriate. They are no longer pre-allocated on the PL/SQL execution stack to their maximum
declared size. This should greatly reduce the memory utilization for applications that pessimistically
declare large VARCHAR2s/RAWs, but often end up storing only small amounts of data in them.
Observe that, for package global VARCHAR2 and RAW variables, this implies savings in UGA
memory. As an optimization, since heap allocated items tend to have a slight performance overhead,
small VARCHAR2s and RAWs are pre-allocated on the PL/SQL execution stack (like in V7).

Serially Reusable Packages

Before Oracle8 PL/SQL, the UGA memory of a package simply stayed around until the end of the
session, whether or not the application needed it anymore. This limits scalability, since such
memory grows linearly with the number of users.

To help applications better manage memory usage, PL/SQL provides the pragma
SERIALLY_REUSABLE, which lets users mark some packages as "serially reusable." You can so
mark a package if its state is needed only for the duration of a call to the server (for example, an OCI
call to the server, a PL/SQL client-to-server or server-to-server RPC).

The global memory for such packages is not kept in the UGA per user, but instead in a small SGA
pool. At the end of the call to the server this memory is returned to the pool for reuse. Before reuse,
the package global variables are initialized to NULL, or to the default values provided.

Techniques For Tuning PL/SQL Applications 18


March 1999
The pool is kept in SGA memory, so that the work area of a package can be reused across users who
have requests for the same package. In this scheme, the maximum number of work areas needed for a
package is only as many as there are concurrent users of the package, which is typically much fewer
than the total number of logged on users. The use of "serially reusable" packages does increase the
shared-pool requirements slightly, but this is more than offset by the decrease in the per-user UGA.
Further, Oracle ages out work areas not in use, when it needs to reclaim shared pool memory.

PL/SQL PERFORMANCE TIPS

As mentioned in the introduction, three steps are required to improve application performance and
scalability. First, analyze the performance of the application, to identify the areas for improvement.
Second, determine the relevant features that can be used to improve the performance. Finally,
develop/modify the application, so that it can use features that extract maximum performance.

This section provides some PL/SQL performance tips for writing efficient PL/SQL code. These tips
can be used to improve the performance of PL/SQL applications.

VARRAYs vs. Nested Tables

Oracle8 offers two new collection types: nested tables and varrays. An important difference is that
varrays have a maximum declared size, whereas nested tables are unbounded. As a result, in Oracle,
varray data is stored in-line (in the same tablespace), but nested table data is stored out-of-line in a
separate table. This implies that storing/retrieving varrays typically involves fewer disk accesses.
Hence, they are more efficient than nested tables. Our experiments indicate that the varrays are 5-
10% faster than nested tables. However, the difference should increase as the varrays are
optimized further.

Nested Tables vs. Index-By Tables

Nested tables require explicit initialization, while index-by tables are automatically initialized. In
addition, nested tables need to be explicitly extended, while index-by tables are automatically
extended when a larger subscript is encountered. As a result, nested tables are densely allocated,
while index-by tables are not. Due to these reasons, nested tables are more efficient than index-by

Techniques For Tuning PL/SQL Applications 19


March 1999
tables. A nested table can replace a dense index-by table, to extract performance benefits in the range
of 10-30% for tables with 100 to 1000 scalar elements.

Integer Operations in PL/SQL

PL/SQL supports PLS_INTEGER as a native datatype: the operations on PLS_INTEGER are


performed by the PL/SQL interpreter itself. Numeric types, such as INTEGER and NUMBER, are
represented in the 22 byte Oracle number format. Oracle number libraries are used to implement
arithmetic operations on these types. Furthermore, the INTEGER type is a constrained subtype of
NUMBER with a precision of 0. On assignments to INTEGER variables, precision checking is done
at run-time.

Both PLS_INTEGER and BINARY_INTEGER are both represented as a signed 4-byte quantity
("sb4"). But, BINARY_INTEGER arithmetic is costly: the operands are first converted to Oracle
number and then the Oracle number library is used to compute the result as another Oracle number.
This results in increased use of temporaries and data conversion, and, hence, poor performance. On
the other hand, native integer arithmetic is used to efficiently implement arithmetic operations on
PLS_INTEGERs.

The numeric types NATURAL, NATURALN, POSITIVE, POSITIVEN, and SIGNTYPE are
subtypes of BINARY_INTEGER (refer to [5] for details) with "stricter" range constraints. There is
considerable overhead (about 3-4 byte-code instructions) in the enforcement of these range
constraints on every assignment (or parameter passing) to variables of these types.

Record of Tables vs. Table of Records

A collection of a related set of values can be stored either as record of tables or table of records. Since
the record of tables requires maintaining tables of scalars, the elements are stored in-line. However,
elements are stored out-of-line for table of records. The table below shows a code fragment that
demonstrates the benefit of using a record of tables.

Techniques For Tuning PL/SQL Applications 20


March 1999
Table of Records Record of Tables

DECLARE DECLARE
... ....
TYPE ae_line_rec_type IS RECORD TYPE num_arr IS TABLE OF number;
( TYPE char30_arr IS TABLE OF varchar2(30);
source_id NUMBER, TYPE char240_arr IS TABLE OF varchar2(240);
source_table VARCHAR2(30), TYPE date_arr IS TABLE OF date;
account NUMBER, TYPE rec_type IS RECORD
entered_dr NUMBER, (
entered_cr NUMBER, source_id num_arr,
accounted_dr NUMBER, source_table char30_arr,
accounted_cr NUMBER, account num_arr,
currency_code VARCHAR2(30), entered_dr num_arr,
exchange_rate_type VARCHAR2(30), entered_cr num_arr,
exchange_rate NUMBER, accounted_dr num_arr,
exchange_date DATE, accounted_cr num_arr,
description VARCHAR2(240), currency_code char30_arr,
third_party_id NUMBER, exchange_rate_type char30_arr,
third_party_site_id NUMBER exchange_rate num_arr,
); exchange_date date_arr,
TYPE tbl_type IS TABLE of rec_type; description char240_arr,
... third_party_id num_arr,
BEGIN third_party_site_id num_arr
... );
END; BEGIN
...
END;

Benchmark results indicate that the record of tables is an order of magnitude faster than the table of
records, for records/tables with 10,000 elements.

Constrained Datatypes

Using NOT NULL constraints in PL/SQL incurs performance penalty. Consider the
following program:

PROCEDURE proc IS

m NUMBER NOT NULL;

a NUMBER;

b NUMBER;

Techniques For Tuning PL/SQL Applications 21


March 1999
BEGIN

m := a + b;

m := m * 1.2;

m := m * m;

...

END;

Since "m" is a NOT NULL constrained number, the result of the expression "a+b" is first computed
into a temporary, and the temporary is then tested, to ensure it is not NULL. If the temporary is
NULL an exception is raised, otherwise the value of the temporary is moved to "m." On the other
hand, if "m" was not constrained, then the result of the expression "a+b" could directly be computed
into "m." So, a more efficient way to rewrite the above fragment with reduced use of temporaries is:

PROCEDURE proc IS

m NUMBER; -- Note: m doesn't have NOT NULL constraint

a NUMBER;

b NUMBER;

BEGIN

m := a + b;

m := m * 1.2;

Techniques For Tuning PL/SQL Applications 22


March 1999
m := m * m;

-- enforce constraint programmatically

IF (m IS NULL) THEN

-- raise appropriate error

END IF;

...

END;

Another thing to note is that the types NATURALN and POSTIVEN are defined to be NOT NULL
subtypes of NATURAL and POSITIVE, respectively. Hence, users will incur the performance
penalty described above when using them. Similarly, assignment among the datatypes with different
range constraints (for example, POSITIVE and BINARY_INTEGER) also incur constraint
checking overhead.

Avoid Type Conversions

PL/SQL does implicit conversions between structurally different types at run-time. Currently, this is
true even when the source item is a literal constant. A common case where implicit conversions
result in a performance penalty, but can be avoided, is with numeric types. For instance, assigning a
PLS_INTEGER variable to a NUMBER variable, or vice versa, will result in a conversion, since their
representations are different. Such implicit conversions can happen during parameter passing as well.

Techniques For Tuning PL/SQL Applications 23


March 1999
Some examples of inefficient code and suggestions to fix them are given below:

•= Prevent conversions between numeric types. For example:

number_variable := number_variable + 1;
The literal constant 1 is represented as a native integer. It gets converted to the Oracle NUMBER
format before the addition. Instead, use:

number_variable := number_variable + 1.0;


The above is more efficient, because literal floats (like 1.0) are represented as Oracle number, so no
type conversion happens at run-time. Or better still, when dealing with integer data, use:

pls_integer_variable := pls_integer_variable + 1;
•= Prevent numeric to character type conversion. For example,

char_variable := 10;
The literal 10 is converted to CHAR at run-time, and then copied. Instead, use:

char_variable := '10';

CONCLUSIONS

PL/SQL is a customer-centric language — Oracle maintains the language specifications and provides
the required execution environment. Oracle intends to continue working with the large PL/SQL
customer base to evolve and enhance the language and execution environment to match user
requirements. Oracle intends to aggressively improve performance, by working on the
following projects:

•= Oracle is considering compilation of PL/SQL to C, which will transparently improve the


performance of all PL/SQL applications by native compilation and avoidance of interpreter overhead.

•= Oracle plans to significantly optimize the runtime engine, to allow applications to be automatically
self tuned, as well as to investigate performance hints at compile time that will help customers
manually tune their applications.

•= Oracle also plans to enhance the profiler, so that customers can find bottlenecks more easily.

Techniques For Tuning PL/SQL Applications 24


March 1999
ACKNOWLEDGEMENTS

The authors of this paper, Ajay Sethi (Editor), Kannan Muthukkaruppan, Chris Racicot, Ashok
Swaminathan, Ron Decker, Radhakrishna Hari, Chandrasekharan Iyer, Sanjay Krishnamurthy, Neil
Le, Shirish Puranik, Ian Stocks and Murali Vemulapati thank the PL/SQL Product Development
team for reviewing the paper. In particular, the authors thank Chandrasekharan Iyer, Thomas
Kurian, Kannan Muthukkaruppan, Shirish Puranik and Ashok Swaminathan for reviewing the paper
and for their useful suggestions for improving the paper.

REFERENCES

1. Scaling to Thousands of Users with Oracle8, Amit Jasuja and William Maimone, European Oracle
User's Conference, Vienna, 1997.

2. Bulk Binds: Faster SQL Execution in PL/SQL, Sanjay Krishnamurthy, Ajay Sethi, and Ashok
Swaminathan, Oracle8i PL/SQL Whitepaper, 1998.

3. Native Dynamic SQL in PL/SQL, Kannan Muthukkaruppan, Neil Le, and Ashok Swaminathan,
Oracle8i PL/SQL Whitepaper, 1998.

4. NOCOPY: Faster Parameter Passing in PL/SQL, Ajay Sethi and Chandrasekharan Iyer, Oracle8i
PL/SQL Whitepaper, 1998.

5. PL/SQL User's Guide and Reference, Oracle8i, 1998.

6. Improving Performance of PL/SQL Applications - Advances in Oracle8, Kannan Muthukkaruppan,


Oracle Open World '97.

7. Oracle8i Server Application Developer's Guide, Oracle8i, 1998.

Techniques For Tuning PL/SQL Applications 25


March 1999
Oracle Corporation
World Headquarters
500 Oracle Parkway
Redwood Shores, CA 94065
U.S.A.

Worldwide Inquiries:
+1.650.506.7000
Fax +1.650.506.7200
http://www.oracle.com/

Copyright © Oracle Corporation 1999


All Rights Reserved

This document is provided for informational purposes only, and


the information herein is subject to change without notice.
Please report any errors herein to Oracle Corporation. Oracle
Corporation does not provide any warranties covering and
specifically disclaims any liability in connection with this
document.

Oracle and ConText are registered trademarks, and Oracle8i,


Oracle8, Oracle7, PL/SQL, Pro*C and Oracle Developer are
trademarks or registered trademarks of Oracle Corporation. All
other company and product names mentioned are used for
identification purposes only and may be trademarks of their
respective owners.

You might also like