Beruflich Dokumente
Kultur Dokumente
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:
•= 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.
•= Some PL/SQL performance tips that can be used for improving application performance
and scalability.
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:
•= Locate bottlenecks.
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.
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.
As summarized in the table below, several new capabilities have been added in Oracle8i PL/SQL in
the areas of performance.
Applications that execute SQL Faster data transfer between Bulk Binds
statements in a loop PL/SQL and SQL
Applications that use the Faster support for dynamic SQL Native dynamic SQL
DBMS_SQL package statements
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:
FOR i In 1..1000
LOOP
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:
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.
word VARCHAR2(20),
meaning VARCHAR(200)
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 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
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.
IS IS
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
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.
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:
BEGIN
IF (n < 2)
RETURN n;
ELSE
END IF;
END;
unsigned int n;
if (n < 2)
return n;
else
Our experiments have shown that C code performs better than PL/SQL, even for values as
small as N=10.
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:
Applications that use the standard builtins Optimization of Package STANDARD 8.1
provided by PL/SQL Builtins
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
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.
•= 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.
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.
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).
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:
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.
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.
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:
•= Scope (procedure frame) entry and exit (by speeding up interpreter's register save/restore operations).
•= Runtime code used for executing the RETURNING INTO clause of SQL DML statements was
tuned to reduce overhead.
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-
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:
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
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.
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:
•= 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.
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
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.
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).
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.
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.
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 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
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.
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.
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
a NUMBER;
b NUMBER;
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
a NUMBER;
b NUMBER;
BEGIN
m := a + b;
m := m * 1.2;
IF (m IS NULL) THEN
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.
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.
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:
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 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.
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.
Worldwide Inquiries:
+1.650.506.7000
Fax +1.650.506.7200
http://www.oracle.com/