Oracle PLSQL Best Practices and Tuning PDF
Oracle PLSQL Best Practices and Tuning PDF
Steven Feuerstein
[email protected]
[email protected]
www.quest.com
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 1
Software Used in Training
PL/Vision: a library of packages installed on top of PL/SQL
– PL/Vision Lite - use it, copy, change it for free -- unless you build
software to be sold commercially
– Advanced PL/SQL Knowledge Base: contains PL/Vision Professional,
the fully supported and enhanced version
Demonstration scripts executed in the training can be found on
the RevealNet PL/SQL Pipeline:
– www.revealnet.com/Pipelines/PLSQL/index.htm plsql_ides.txt
Exposed implementation!
– show me how it is getting the job done
Hard-coding
– assumes that something will never change
-- and that is never going to not happen
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 6
Hard-Coding in PL/SQL
Literal values IF num_requests > 100 THEN
Date format in a call to TO_DATE 'MM/DD/YY'
Language specificities '09-SEP-2001'
Constrained declarations NUMBER(10,4)
Variables declared using base datatypes my_name VARCHAR2(20)
Every SQL statement you write, especially implicit cursors
COMMIT and ROLLBACK statements
Fetching into a list of individual variables
Embedded (unencapsulated) business rules
Scary,
isn't it?
When you are done with this seminar, you will know
how to get rid of all these kinds of hard-coding.
features in Oracle,
Executor
etc.
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 8
Expectations for a Training on
Tuning
Improve the
performance of your
application 1000
fold for only $19.95
a month!*
EXECUTE prof_report_utilities.rollup_all_runs;
/* Total time */
SELECT TO_CHAR (grand_total / 1000000000, '999999.99') AS grand_total
FROM plsql_profiler_grand_total;
them every_tmr.go;
FOR indx IN 1 .. &1
thisuser.*
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 23
Calculate Overhead
We often wonder how
DECLARE
otmr tmr_t :=
tmr_t.make ('OPEN?');
expensive is a particular ftmr tmr_t :=
tmr_t.make ('FETCH?');
actually performs a
SELECT *
from plsql_profiler_data
Order BY total_time DESC;
requested task lots_rec lots_stuff%ROWTYPE;
Common questions:
BEGIN
otmr.go;
OPEN lots_stuff;
– Does Oracle identify the otmr.stop;
Understanding PL/SQL
Architecture
User 1 9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 26
Code in Shared Memory
PL/SQL is an interpretative language. The source code is
“partially compiled” into an intermediate form (“p-code”).
– The p-code is loaded into the shared pool when any element of
that code (package or stand-alone program) is referenced.
The partially-compiled code is shared among all users who
have EXECUTE authority on the program/package.
– Prior to Oracle7.3, contiguous memory was required for program
units. That is now relaxed, but still preferable.
Each user (Oracle session) has its own copy of any data
structures defined within the program/package.
– Separate sets of in-memory data (not shared among different
users) are stored in the PGA.
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 27
PL/SQL Tuning and Best
Practices
Writing SQL in PL/SQL
Order Item
Table Table
Dept Code
PROCEDURE calc_totals
IS
v_dname VARCHAR2(20);
v_ename CHAR(30);
BEGIN
SELECT dname, ename
Emp INTO v_dname, v_ename
This program is a
ticking time bomb
FROM emp, dept
in my
WHERE ...;
application...
...
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 31
The View from 30,000 Feet
High-Level Best Practices
– If the answer is "not really", then you have essentially lost control
of your application code base
It is crucial that you avoid repetition of the same logical
SQL statement...
– With repetition, comes variation, and with it excessive parsing
– Potentially significant impact on performance and maintainability
Order Item
Table Table
Move all SQL inside packages: one per table or "business object"
– All DML statements written by an expert, behind a procedural interface,
with standardized exception handling
– Commonly-needed cursors and functions to return variety of data (by
primary key, foreign key, etc.)
– If the encapsulation package doesn't have what you need, add the new
element, so that everyone can take advantage of it
– Separate packages for query-only and change-related functionality (for
added security) te_employee.*
givebonus*.sp
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 39
Allow No Exceptions!
Instead of this:
INSERT INTO employee
(employee_id, department_id, salary, hire_date) TRUE STORY!
VALUES "I forced all programmers to
(1005, 10, 10000, SYSDATE); use the encapsulated
INSERT, instead of writing
Do this: their own. Using SQLab, we
determined that this one
te_employee.insert ( insert statement was parsed
employee_id_in => 1005, department_id_in => 10, 1 time and executed over a
salary_in => 10000, hire_date_in => SYSDATE); million times! It has been in
the SGA for over 2 weeks,
Check dependency information to identify never aging out because it is
called so frequently."
programs that rely directly on tables
SELECT owner || '.' || name refs_table,
REFERENCED_owner || '.' || REFERENCED_name table_referenced
FROM ALL_DEPENDENCIES
WHERE type IN ('PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'FUNCTION')
AND REFERENCED_type IN ('TABLE', 'VIEW');
reftabs.sql
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 40
Minimal Encapsulation a Must!
At an absolute minimum, hide every single row query behind the
header of a function
– If you hide the query, you can choose (and change) the implementation
for optimal performance
variable:
totsales NUMBER (10,2);
CLOSE company_pkg.allrows;
Fetching into a record
means writing
rec company_pkg.allrows%ROWTYPE; less code.
r BEGIN
I OPEN company_pkg.allrows;
FETCH company_pkg.allrows INTO rec;
g If the cursor select list
changes, it doesn't
h IF rec.name = ‘ACME’ THEN ...
necessarily affect your
t CLOSE company_pkg.allrows;
code.
PROCEDURE del_scenario
IS
reg_cd VARCHAR2(100) := :GLOBAL.reg_cd; No problem!
BEGIN
DELETE FROM scenarios
WHERE reg_cd = del_scenario.reg_cd
AND scenario_id = :scenario.scenario_id;
END; delscen.sql
delscen1.sql
delscen2.sql
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 52
Write SQL Efficiently in PL/SQL
Do this:
UPDATE emp SET sal = sal * 1.01; allsql.tst
allsql2.tst
Do this:
BEGIN
INSERT INTO UnionBuster VALUES (ub_seq.NEXTVAL, 'Prison', 5)
RETURNING ub_id, hourly_wage
INTO v_latest_bustID, v_hard_to_beat;
END;
returning.tst
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 56
Use WHERE CURRENT OF
When using SELECT FOR UPDATE, use the WHERE
CURRENT OF clause in UPDATE and DELETE to avoid
coding a possibly complex and slower WHERE clause.
Instead of this:
LOOP
FETCH cur INTO rec;
EXIT WHEN cur%NOTFOUND;
Do This:
LOOP
FETCH cur INTO rec;
EXIT WHEN cur%NOTFOUND;
Performance penalty
for many “context
switches”
Some restrictions:
– Only the single DML statement is allowed. If you want to INSERT
and then UPDATE, two different FORALL statements
– Cannot put an exception handler on the DML statement
– Returns each
hdates dateTab;
BEGIN
selected expression
SELECT empno, ename, hiredate
BULK COLLECT INTO enos, names, hdates
in a table of scalars
FROM emp
WHERE deptno = deptno_in;
emplist.EXTEND(enos.COUNT);
FOR i IN enos.FIRST..enos.LAST
LOOP
emplist(i) := emp_t(enos(i),
names(i), hiredates(i));
END LOOP;
RETURN emplist;
END;
RETURN enolist;
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 63
Tips and Fine Points
Use bulk binds if you write code with these characteristics:
– Recurring SQL statement in PL/SQL loop
– Use of a collection as the bind variable, or code that could be
transformed to use a collection containing the bind variable
information
Bulk bind rules:
– Can be used with any kind of collection
– Collection subscripts cannot be expressions
– The collections must be densely filled
– If error occurs, prior successful DML statements are NOT ROLLED
BACK
Bulk collects:
– Can be used with implicit and explicit cursors
– Collection is filled starting at row 1
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 64
Optimizing DBMS_SQL Usage
DBMS_SQL implements dynamic SQL in PL/SQL
– It is a very complex and difficult to use package
fdbk PLS_INTEGER;
BEGIN
DBMS_SQL.BIND_ARRAY (cur, 'ename', enametab);
DBMS_SQL.CLOSE_CURSOR (cur);
END;
dyndel.sp
dyndel.tst
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 70
Returning Values After Execution
With Oracle8, you can include a RETURNING clause
– Obtain info about rows just modified without issuing extra query
CREATE OR REPLACE PROCEDURE delemps (
enametab IN DBMS_SQL.VARCHAR2_TABLE)
IS
cur PLS_INTEGER := PLVdyn.open_and_parse (
'DELETE FROM emp WHERE ename LIKE UPPER (:ename) ' ||
' RETURNING empno INTO :empnos');
empnotab DBMS_SQL.NUMBER_TABLE; Notice use of
VARIABLE_VALUE
fdbk PLS_INTEGER;
BEGIN
DBMS_SQL.BIND_ARRAY (cur, 'ename', enametab);
DBMS_SQL.BIND_ARRAY (cur, 'empnos', empnotab);
fdbk := DBMS_SQL.EXECUTE (cur);
DBMS_SQL.VARIABLE_VALUE (cur, 'empnos', empnotab);
BEGIN
DBMS_SQL.parse (cur,
'INSERT INTO emp (empno, deptno, ename)
VALUES (:empno, :deptno, :ename)',
DBMS_SQL.native);
DBMS_SQL.close_cursor (cur);
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 76
Writing SQL in PL/SQL
Summary
Package Construction
package
('set_last_date', 2, date_in);
Data Window
External
Switch On
Switch Program
Flag
Switch Off
Toggles are...
– On/off switches that allow you to modify the behavior of the package
without changing its code
Windows offer...
– Read-only access to the inner workings of a package
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 84
A Window Offers Safe Viewing
External
Data Window Program
yes
Application Is feature Take requested
requests action turned on? action
no Continue normal
execution
DBMS_OUTPUT.PUT_LINE (P_and_L.stmt_date);
IF P_and_L.stmt_date > SYSDATE - 4
THEN
DBMS_OUTPUT.PUT_LINE (SYSDATE - 1);
P_and_L.set_stmt_date (SYSDATE - 1);
END IF;
END;
PROCEDURE notrc IS
Procedures set the
BEGIN flag's value, while the
g_trace := FALSE;
END;
function returns
the current value.
FUNCTION tracing RETURN BOOLEAN IS
BEGIN
RETURN g_trace;
END;
/* MORE ON NEXT PAGE
*/
END dynsql;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 90
Trace Window & Toggle
Implementation
CREATE OR REPLACE PACKAGE BODY p_and_l
IS
PROCEDURE set_stmt_date (date_in IN DATE)
IS
BEGIN
IF tracing
THEN
Check status watch.action ('set_stmt_date', date_in);
of toggle. END IF;
PROCEDURE toscreen
IS Request a
BEGIN write to screen.
g_target := c_screen;
END;
PROCEDURE topipe
IS Request a
BEGIN write to pipe.
g_target := c_pipe;
END;
IF g_target = c_screen
THEN Write to screen.
p.l (msg);
User View
Display Data
Package Spec
Display Date Display Number Display String
Diamond facets
are the programs
in the Package Body
specification
Display Formatted Text
Your challenge: to pull all the facets back to a point, a Bottom point of
single implementation. diamond: the
implementation
p.spb
parse_update ddl
open_and_parse
display_data DBMS_SQL.
DBMS_SQL.PARSE
OPEN_CURSOR
PLVprs.display_wrap
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 100
Splitting up Large Packages
It is certainly possible to create stored code -- especially
packages -- which become too large
– Too large to compile
– Too large to maintain effectively
– Too large to enhance in a reasonable span of time
What can you do when you have created "monsters"?
– Modularize ruthlessly, avoiding any kind of code repetition by using
nested modules or private programs in the body
– You can also split up large packages into separate program units to
make sure that you can compile -- and incrementally compile --
without lengthy delays
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 101
Lots of Code, Lots of Headaches
The problem is with the body (all that code), not the
specification
"Deep" (large)
programs
Before...
Global, private
data Data
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 102
After: Break Up that Big Body
Keep the public specification intact so existing
code is not affected by the change Shallow
public
package
Create a
"virtual
package"
through
privileges.
Data
Move global
data into
"hidden"
pkg spec.
splitpkg.pkg
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 103
Package Construction
Summary
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 104
PL/SQL Tuning & Best Practices
Modularization & Encapsulating Logic
Modularizing fundamentals
Encapsulation scenarios
The dangers of over-abstraction
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 105
Back to Modularizing
Fundamentals
Modularize to reduce complexity, make your tasks
manageable, and make your resulting code maintainable
Tried-and-tested guidelines for 3GLs hold true for PL/SQL
– Code Complete by Steve McConnell is chock-full of
recommendations
Two to keep in mind:
– Modularize with top-down design; keep executable sections
small and easy to read
– Avoid multiple RETURN statements in executable section
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 106
Keeping Executable Code Tight
Most of the time when we write programs, we end up with big blobs
of code spaghetti
the declaration
IS
SELECT * FROM emp WHERE deptno = department_in;
BEGIN /* main */
The code is now FOR emp_rec IN emp_cur
easier to read and LOOP
IF analysis.caseload (emp_rec.emp_id) <
maintain analysis.avg_cases (department_in);
THEN
You can more assign_next_open_case (emp_rec.emp_id, case#);
schedule_case
easily identify (case#, next_appointment (case#));
areas for END IF;
END LOOP
locmod.sp
utgen.pkb
improvement END assign_workload;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 108
Local Modules Avoids Redundancy
Even if local modules don't have a dramatic impact on code
volume, the extra effort to avoid redundancy today is a valuable
insurance policy against maintenance costs in the future
PROCEDURE calc_percentages (total_in IN NUMBER)
IS
BEGIN
food_sales_stg := formatted_pct (sales.food_sales);
service_sales_stg := formatted_pct (sales.service_sales);
toy_sales_stg := formatted_pct (sales.toy_sales);
END;
locmod.sp
hashdemo.sp 9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 109
Avoid Multiple RETURNS in Functions
Try to follow the general rule: one way in, one way out
– Multiple RETURNs in the executable section make it error-
prone and difficult to maintain
FUNCTION status_desc ( FUNCTION status_desc (
cd_in IN VARCHAR2) cd_in IN VARCHAR2)
RETURN VARCHAR2 RETURN VARCHAR2 Now it always
IS IS RETURNs
BEGIN retval VARCHAR2(20); something.
IF cd_in = 'C' BEGIN
THEN RETURN 'CLOSED'; IF cd_in = 'C'
THEN retval := 'CLOSED';
ELSIF cd_in = 'O' ELSIF cd_in = 'O'
THEN RETURN 'OPEN'; THEN retval := 'OPEN';
ELSIF cd_in = 'I'
ELSIF cd_in = 'I' THEN retval := 'INACTIVE';
THEN RETURN 'INACTIVE'; END IF;
END IF; RETURN retval;
END; END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 111
Developing the Encapsulation
Reflex
We'll explore several examples of encapsulation
(a.k.a., abstraction):
– Implementing transaction flexibility
– Working around the limitations of DBMS_OUTPUT
– Storing knowledge so that it won't be forgotten
– Using assertion routines
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 112
Make the Extra Effort!
The path of "minimal implementation" taken (often) by Oracle:
PACKAGE DBMS_UTILITY
IS
PROCEDURE comma_to_table( All lists manipulated by PL/SQL
list IN VARCHAR2, developers are delimited by
tablen OUT BINARY_INTEGER, commas, right?
tab OUT uncl_array);
FUNCTION string_to_table
(list_in VARCHAR2, delim IN VARCHAR2 := ',')
RETURN uncl_array;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 113
It's Hard to Achieve
Effective Reuse
We need all of the following...
Quality code that is worth reusing
Central repository for code
Means of locating the right piece of code
Good documentation of code in the repository
Management commitment to supporting reuse
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 114
Transaction Management
Flexibility
A simple enough recommendation:
Never call COMMIT; in your PL/SQL code
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 116
Guys Don't Wanna COMMIT -
Reason 2
They need to update 1M rows and find themselves running out
of rollback segments or getting "snapshot too old" errors
– So you have to commit "every N records"
commit_counter := 0
FOR original_rec IN original_cur
LOOP
translate_data (original_rec);
Surely you have
IF commit_counter >= 10000 better things to
THEN do with your time
COMMIT; than write code
commit_counter := 0; like this!
ELSE
commit_counter := commit_counter + 1;
END IF;
END LOOP;
COMMIT;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 117
Guys Don't Wanna COMMIT -
Reason 3
They use a non-default rollback segment, and that darn
COMMIT resets the active RB segment to the default
– The "fix" is easy: you just have to remember to call
DBMS_TRANSACTION.USE_ROLLBACK_SEGMENT every time you
execute a COMMIT
– And you have to remember the name of that obscure program!
BEGIN
Here's
COMMIT;
one!
DBMS_TRANSACTION.USE_ROLLBACK_SEGMENT ('big_one');
FOR I IN 1 .. max_years
Uh oh! do_stuff;
There's COMMIT;
another one! DBMS_TRANSACTION.USE_ROLLBACK_SEGMENT ('bigone');
END LOOP;
Whoops! Typo!
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 118
Who Needs Those
Complications?
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 119
Intercepting and Modifying a
Commit
Use toggles to override and even turn off default processing
Application By calling
requests commit PLVcmt.perform_commit
Is trace
no
Finally, the real
turned on?
thing!
yes
yes
Display or log Is commit COMMIT;
information turned on?
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 120
Using the COMMIT Alternative
The following program uses the PL/Vision alternative
to COMMIT
CREATE OR REPLACE PROCEDURE myapp (counter IN INTEGER)
IS
BEGIN
/* Define the current rollback segment. */ Set the non-default
PLVcmt.set_rbseg ('bigseg'); rollback segment.
PLVcmt.perform_commit (
'DELETED ' || SQL%ROWCOUNT ||
' on iteration ' || cmtind); While committing,
END LOOP; also pass trace
END; information.
cmt.tst
plvcmt.sps
plvcmt.spb
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 121
Simplify Incremental Commit Logic
The following program commits every N record
– But the N is not fixed in the code
– Much less code needs to be written
PROCEDURE update_seven_million_rows (every_n IN INTEGER)
IS
BEGIN Set the increment and
PLVcmt.commit_after (every_n); initialize the counter.
PLVcmt.init_counter;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 122
The Added Value Adds Up
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 123
Storing Acquired Knowledge
We learn new techniques after significant investment of research
time, and then we lose access to that new knowledge
– The result is lowered productivity and redundant code
February 1998 April 1998 February 1999
After three hours, Where did I put that Primary keys, wait a
I figured it all out -- and stuff on primary keys? minute, there's a note
I buried it in my current Aw heck, I'll just write it on my terminal: see
program. again. checktab.sql.
PLVinds PLVcons
pky.tst
tabhaslong.sf
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 125
Let's Talk About DBMS_OUTPUT
PL/SQL 1 had ZERO tracing/output capability, causing an
uproar from the developer community
So Oracle Corporation gave us DBMS_OUTPUT, and it:
– Requires the typing of 20 letters just to call the darned thing
– Knows nothing about the Boolean datatype
– Ignores my requests to display blank lines and trims leading blanks
– Raises VALUE_ERROR if the string has more than 255 bytes
– Refuses to display anything until the block finished executing?
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 126
Saving Yourself From
DBMS_OUTPUT
Just say to yourself: "I will never call that thing in my
application, instead I will call my own, better alternative"
DBMS_OUTPUT
Application
Software
Overlay
Package
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 127
Constructing a Useful Substitute
PROCEDURE p.l (line_in IN VARCHAR2) IS Don’t ignore leading
BEGIN blanks or blank lines.
IF RTRIM (line_in) IS NULL
THEN
DBMS_OUTPUT.PUT_LINE (g_prefix);
ELSE
DBMS_OUTPUT.PUT_LINE (g_prefix || line_in);
END IF;
EXCEPTION
Anticipate and attempt
WHEN OTHERS
to correct common
THEN
problems.
DBMS_OUTPUT.ENABLE (1000000);
DBMS_OUTPUT.PUT_LINE (g_prefix || line_in);
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 128
Validation of Assumptions
Every module has its assumptions
– A certain set of key data is available, or dates must be
within a specific range
– If you don't validate those assumptions, your programs
will break in unpredictable, ugly ways
Code defensively
– Assume that developers will not use your programs
properly
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 129
Traditional Approach to Validation
Use conditional logic to perform the checks
If department pointer is
PROCEDURE calc_totals (
NULL, display message &
dept_in IN INTEGER, date_in IN DATE) IS
raise error.
BEGIN
IF dept_in IS NULL
THEN
p.l ('Provide a non-NULL department ID.');
RAISE VALUE_ERROR;
END IF;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 130
The Problems with Business-as-Usual
expressions repeatedly
stg_in IN VARCHAR2 := NULL);
PROCEDURE assert_notnull
– Bury the repetition in a (val_in IN BOOLEAN|DATE|NUMBER|VARCHAR2,
variety of assertion stg_in IN VARCHAR2 := NULL);
. . .
plv.sps
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 135
Using Specialized Assertions
Let's take a final look at the calc_totals procedure, this
time using assertion programs designed to check for
specific types of conditions
PROCEDURE calc_totals
(dept_in IN INTEGER, date_in IN DATE)
IS
BEGIN
PLV.assert_notnull (dept_in,
'Provide a non-NULL department ID.');
PLV.assert_inrange (date_in,
ADD_MONTHS (SYSDATE, -60), SYSDATE,
'Date is out of range.');
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 136
The Dangers of Excessive
Encapsulation
Suppose that I am building an application that needs to make
extensive use of UTL_FILE to write the contents of a variety of
tables to files
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 138
Layers of Abstraction with PLVrep
Data Merge Application
Case Converter
Process Server
PLVdate PLVtab
PLVbool PLVofst
UTL_FILE DBMS_PIPE DBMS_SQL
PLVstr PLVseg
Generic PL/SQL Repository Functions
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 139
Flow of Generic Repository Mgt.
Allocates memory to keep track of
Define information about the repository.
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 140
PLVrep Tackles "Table to File"
CREATE OR REPLACE PROCEDURE tab2file (
tab IN VARCHAR2, file IN VARCHAR2 := NULL,
delim IN VARCHAR2 := ',')
IS
tid PLS_INTEGER := PLVrep.dbtabid (tab);
fid PLS_INTEGER :=
PLVrep.fileid (NVL (file, tab || '.dat'),
fixedlen=>PLV.ifelse (delim IS NULL, TRUE, FALSE));
BEGIN
PLVrep.tabsegs (tid, tab); -- Make the repositories look like
PLVrep.tabsegs (fid, tab); -- the table.
PLVrep.copy (tid, fid, segdelim => delim);
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 141
Problems with PLVrep
Too complicated
– 143 procedures and functions
– A robust new vocabulary for PL/SQL developers ?!?!
Too generic
– It tries to achieve (and largely accomplishes) too much.
– Feature set overwhelms potential advantage of a “single language”
Too slow
– The tradeoff for being so generic, for hiding so much complexity, is
that PLVrep has to do lots of work on your behalf
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 142
What is High Quality Software?
Is it a "perfectly" designed and
implemented system, built upon
many layers of abstracted,
theoretically reusable code?
– Is such a thing (a) truly desirable
or (b) even possible? or…
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 143
Let's Talk About Habitability
"Habitability is the characteristic of a piece of source code that
enables programmers, coders, bug-fixers, and people coming to
the code later in its life to understand its construction and
intentions, and to change it comfortably and confidently.“
tab2file.txt
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 146
Resolving the Dilemma w/Code
Generation
So we have a bind:
– Write (and learn how to use)
Quality
highly abstracted, complex
code, or… Artistry
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 147
Generate "Habitable" Code?
[STOREIN][objname]2file.sp
CREATE OR REPLACE PROCEDURE [objname]2file (
loc IN VARCHAR2,
file IN VARCHAR2 := '[objname].dat',
delim IN VARCHAR2 := '|'
) Template captures pattern.
IS
fid UTL_FILE.FILE_TYPE;
[IF]oraversion[IN]8.0,8.1 Utility generates code from
line VARCHAR2(32767); template for a specific object.
[ELSE]
line VARCHAR2(1023);
[ENDIF] Resulting code is readily
BEGIN understandable and easily
fid := maintained.
UTL_FILE.FOPEN (loc, file, 'W');
FOR rec IN (SELECT * FROM [objname])
LOOP
line :=
[FOREACH]col
[IF][coldatatype][eq]VARCHAR2
...
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 148
Encapsulation Recommendations
Build small encapsulations/abstractions
– If I need to take a week-long class to learn how to use the
abstraction/library, I will never really (re)use that code
Keep hierarchies shallow as long as possible
– If you make a mistake along the way, you can fix it with a minimum of
impact
Build incrementally
– Avoid “master plans”; build in direct response to programmer needs
Don't accept performance trade-offs for abstraction
– It is very rarely justified in a production environment
Find or build tools/utilities to leverage abstractions
– The toughest challenge of all!
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 149
PL/SQL Best Practices
Developing an
Exception Handling
Architecture
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 150
Exception Handling in PL/SQL
Execute Application Code
Handle Exceptions
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 151
You Need Strategy &
Architecture
To build a robust PL/SQL application, you need to decide on
your strategy for exception handling, and then build a code-
based architecture for implementing that strategy
In this section, we will:
– Explore the features of PL/SQL error handling to make sure we have
common base of knowledge
– Examine the common problems developers encounter with exception
handling
– Construct a prototype for an infrastructure component that enforces a
standard, best practice-based approach to trapping, handling and
reporting errors The PL/Vision PLVexc
package is a more complete
implementation.
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 152
Flow of Exception Handling
PROCEDURE financial_review
IS
BEGIN PROCEDURE calc_profits
calc_profits (1996); IS BEGIN
numeric_var := 'ABC';
calc_expenses (1996);
EXCEPTION
DECLARE WHEN VALUE_ERROR THEN
v_str VARCHAR2(1); log_error;
PROCEDURE calc_expenses BEGIN RAISE;
IS BEGIN v_str := 'abc'; END;
... EXCEPTION
EXCEPTION WHEN VALUE_ERROR THEN
WHEN NO_DATA_FOUND . . .
THEN END;
log_error; EXCEPTION
END; WHEN OTHERS
THEN
...
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 153
Scope and Propagation
Reminders
You can never go home
– Once an exception is raised in a block, that block's executable section
closes. But you get to decide what constitutes a block
Once an exception is handled, there is no longer an exception
(unless another exception is raised)
– The next line in the enclosing block (or the first statement following the
return point) will then execute
If an exception propagates out of the outermost block, then that
exception goes unhandled
– In most environments, the host application stops
– In SQL*Plus and most other PL/SQL environments, an automatic
ROLLBACK occurs
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 154
What the Exception Section Covers
The exception section only handles exceptions raised in the
executable section of a block
– For a package, this means that the exception section only handles
errors raised in the initialization section
DECLARE
Declarations
BEGIN
Executable
Statements
EXCEPTION
Exception
Handlers
END
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 155
Continuing Past an Exception
Emulate such behavior by enclosing code within its own block
PROCEDURE cleanup_details (id_in IN NUMBER) IS
BEGIN
DELETE FROM details1 WHERE pky = id_in;
All or DELETE FROM details2 WHERE pky = id_in;
Nothing END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 156
Exceptions and DML
DML statements are not rolled back by an exception unless it
goes unhandled
– This gives you more control over your transaction, but it also can
lead to complications
– What if you are logging errors to a database table? That log is then
a part of your transaction
You may generally want to avoid "unqualified" ROLLBACKs
and instead always specify a savepoint
EXCEPTION
WHEN NO_DATA_FOUND THEN
ROLLBACK TO last_log_entry;
INSERT INTO log VALUES (...);
SAVEPOINT last_log_entry;
But it can get
END;
complicated!
lostlog*.sql
rb.pkg
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 157
Application-Specific Exceptions
Raising and handling an exception specific to the application requires
special treatment
– This is particularly true in a client-server environment with Oracle
Developer
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 158
Communicating an Application Error
Use the RAISE_APPLICATION_ERROR built-in procedure to
communicate an error number and message across the client-server
divide
– Oracle sets aside the error codes between -20000 and -20999 for your
application to use. RAISE_APPLICATION_ERROR can only be used those
error numbers
RAISE_APPLICATION_ERROR
(num binary_integer,
msg varchar2,
keeperrorstack boolean default FALSE);
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 159
Handling App. Specific Exceptions
– Handle in OTHERS with check against SQLCODE...
BEGIN
INSERT INTO emp (empno, deptno, birthdate)
VALUES (100, 200, SYSDATE);
Server-side
EXCEPTION
WHEN OTHERS THEN
Database
IF SQLCODE = -20070 ...
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 160
The Ideal But Unavailable
Solution
-- Declare the exception in one place (server) and reference it (the error number or
name) throughout your application.
Server side pkg
CREATE OR REPLACE PACKAGE emp_rules IS
defines exception.
emp_too_young EXCEPTION;
END;
BEGIN
INSERT INTO emp VALUES (100, 200, SYSDATE);
EXCEPTION Client side block
WHEN emp_rules.emp_too_young THEN ... handles exception.
END;
But this won't work with Oracle Developer! If it's got a dot and is defined
on the server, it can only be a function or procedure, not an exception or
constant or variable...
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 161
Blocks within Blocks I
– What information is displayed on your screen when you
execute this block?
DECLARE
aname VARCHAR2(5);
BEGIN
BEGIN
aname := 'Justice';
DBMS_OUTPUT.PUT_LINE (aname);
EXCEPTION
WHEN VALUE_ERROR
THEN
DBMS_OUTPUT.PUT_LINE ('Inner block');
END;
DBMS_OUTPUT.PUT_LINE ('What error?');
EXCEPTION
WHEN VALUE_ERROR
THEN excquiz1.sql
DBMS_OUTPUT.PUT_LINE ('Outer block');
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 162
Blocks within Blocks II
– What information is displayed on your screen when you
execute this block?
DECLARE
aname VARCHAR2(5);
BEGIN
DECLARE
aname VARCHAR2(5) := 'Justice';
BEGIN
DBMS_OUTPUT.PUT_LINE (aname);
EXCEPTION
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE ('Inner block');
END;
DBMS_OUTPUT.PUT_LINE ('What error?');
EXCEPTION
WHEN VALUE_ERROR THEN excquiz2.sql
DBMS_OUTPUT.PUT_LINE ('Outer block'); excquiz2a.sql
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 163
Blocks within Blocks III
What do you see when you execute this block?
DECLARE
aname VARCHAR2(5);
BEGIN
<<inner>>
BEGIN
aname := 'Justice';
EXCEPTION
WHEN VALUE_ERROR THEN
RAISE NO_DATA_FOUND;
EXCEPTION excquiz3.sql
WHEN NO_DATA_FOUND THEN excquiz6.sql
excquiz6a.sql
DBMS_OUTPUT.PUT_LINE ('Outer block');
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 164
Blocks within Blocks IV
What do you see when you execute this block?
– Assume that there are no rows in emp where deptno equals -15
DECLARE
v_totsal NUMBER;
v_ename emp.ename%TYPE;
BEGIN
SELECT SUM (sal) INTO v_totsal FROM emp WHERE deptno = -15;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 165
Taking Exception to My Exceptions
What do you see when you execute this block?
DECLARE
d VARCHAR2(1);
no_data_found EXCEPTION;
BEGIN
SELECT dummy INTO d
FROM dual
WHERE 1=2;
IF d IS NULL
THEN
RAISE no_data_found;
END IF;
EXCEPTION
WHEN no_data_found excquiz5.sql
THEN
DBMS_OUTPUT.PUT_LINE ('No dummy!');
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 166
Where Did the Error Occur?
When an error occurs inside your code, the most
critical piece of information is the line number of the
program in which the error was raised
How can you obtain this information?
A. DBMS_UTILITY.FORMAT_ERROR_STACK
B. Unhandled exception
C. DBMS_UTILITY.FORMAT_CALL_STACK
D. Write error information to log file or table
disperr*.tst
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 167
What, No GOTO?
It gets the job done…but does the end justify the means?
FUNCTION totalsales (year IN INTEGER) RETURN NUMBER
IS
return_nothing EXCEPTION;
return_the_value EXCEPTION;
retval NUMBER;
BEGIN
retval := calc_totals (year);
IF retval = 0 THEN
RAISE return_nothing;
ELSE
RAISE return_the_value;
END IF;
EXCEPTION
WHEN return_the_value THEN RETURN retval; isvalinlis.sql
WHEN return_nothing THEN RETURN 0;
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 168
An Exceptional Package
PACKAGE BODY valerr
PACKAGE valerr IS
IS v VARCHAR2(1) := ‘abc’;
FUNCTION FUNCTION get RETURN VARCHAR2 IS
get RETURN VARCHAR2; BEGIN
END valerr; RETURN v;
END;
BEGIN
p.l ('Before I show you v...');
EXCEPTION
WHEN OTHERS THEN
p.l (‘Trapped the error!’);
END valerr;
valerr.pkg
valerr2.pkg
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 169
Desperately Seeking Clarity
Hopefully everyone now feels more confident in their
understanding of how exception handling in PL/SQL
works
If every developer writes exception handler code on their own, you end up
with an unmanageable situation
– Different logging mechanisms, no standards for error message text,
inconsistent handling of the same errors, etc.
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 171
Some Dos and Don'ts
Make decisions about exception handling before starting your
application development. Here are my recommendations:
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 172
Checking Standards Compliance
Whenever possible, try to move beyond document-based
standards
– Instead, build code to both help people deploy standards and
create tools to help verify that they have complied with standards
CREATE OR REPLACE PROCEDURE progwith (str IN VARCHAR2)
IS
CURSOR objwith_cur (str IN VARCHAR2)
IS
SELECT DISTINCT name
FROM USER_SOURCE
WHERE UPPER (text) LIKE '%' || UPPER (str) || '%';
BEGIN
FOR prog_rec IN objwith_cur (str)
LOOP
p.l (prog_rec.name); valstd.pkg
END LOOP;
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 173
Pre-Defined -20,NNN Errors
PACKAGE errnums
IS
Assign Error
en_general_error CONSTANT NUMBER := -20000;
Number
exc_general_error EXCEPTION;
PRAGMA EXCEPTION_INIT
(exc_general_error, -20000);
END errnums;
END errpkg;
Message Text
Consolidator errpkg.pkg
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 175
Implementing a Generic RAISE
Hides as much as possible the decision of whether to do a normal
RAISE or call RAISE_APPLICATION_ERROR
– Also forces developers to rely on predefined message text
PROCEDURE raise (err_in IN INTEGER) IS
BEGIN
IF err_in BETWEEN -20999 AND -20000
THEN
RAISE_APPLICATION_ERROR (err_in, errtext (err_in));
ELSIF err_in IN (100, -1403)
Enforce use
THEN
RAISE NO_DATA_FOUND;
of standard
ELSE message
PLVdyn.plsql (
'DECLARE myexc EXCEPTION; ' ||
' PRAGMA EXCEPTION_INIT (myexc, ' ||
TO_CHAR (err_in) || ');' || Re-raise almost
'BEGIN RAISE myexc; END;'); any exception using
END IF; Dynamic PL/SQL!
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 176
Raising Application Specific Errors
With the generic raise procedure and the pre-defined error numbers,
you can write high-level, readable, maintainable code
– The individual developers make fewer decisions, write less code, and
rely on pre-built standard elements
Let's revisit that trigger logic using the infrastructure elements...
PROCEDURE validate_emp (birthdate_in IN DATE) IS
BEGIN
IF ADD_MONTHS (SYSDATE, 18 * 12 * -1) < birthdate_in
THEN
errpkg.raise (errnums.en_must_be_18);
END IF;
END;
No more hard-coded
strings or numbers.
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 177
Deploying Standard Handlers
The rule: developers should only call a pre-defined handler inside
an exception section
– Make it impossible for developers to not build in a consistent, high-quality
way
– They don't have to make decisions about the form of the log and how the
process should be stopped
EXCEPTION
WHEN NO_DATA_FOUND
THEN
errpkg.recNgo (
SQLCODE,
' No company for id ' || TO_CHAR (v_id));
WHEN OTHERS
THEN The developer simply
errpkg.recNstop; describes
END; the desired action.
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 178
Implementing a Generic Handler
Hides all details of writing to the log, executing the handle action
requested, etc.
PACKAGE BODY errpkg
IS
PROCEDURE recNgo (err_in IN INTEGER := SQLCODE,
msg_in IN VARCHAR2 := NULL)
IS
BEGIN
log.put (err_in, NVL (msg_in, errtext (err_in)));
END;
END errpkg;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 179
Retrieving Consolidated
Message Text
FUNCTION errtext (err_in IN INTEGER := SQLCODE) RETURN VARCHAR2 IS
CURSOR txt_cur IS
SELECT text FROM message_text
WHERE texttype = 'EXCEPTION' AND code = err_in;
txt_rec txt_cur%ROWTYPE;
BEGIN
OPEN txt_cur;
FETCH txt_cur INTO txt_rec;
IF txt_cur%NOTFOUND THEN
txt_rec.text := SQLERRM (err_in);
END IF;
You can selectively
RETURN txt_rec.text;
override default,
END;
cryptic Oracle messages.
Or, as shown in the errpkg.pkg file, you can call the underlying
msginfo packaged function to retrieve the text from that standardized
component
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 180
Added Value of a Handler
Package
Once you have all of your developers using the handler
package, you can add value in a number of directions:
– Store templates and perform runtime substitutions
– Offer the ability to "bail out" of a program, no matter how many
layers of nesting lie between it and the host application
exc.tst
exc2.tst
exc3.tst
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 181
An Exception Handling Architecture
Summary
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 182
PL/SQL Tuning & Best Practices
Control Contention
in
Multi-User Systems
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 183
Sharing Resources
Anyone writing single-user applications can skip
this topic.
– Any takers?
Contention for shared resources can skewer
performance of the most perfectly written pl/sql
programs.
Many contention issues have their origin in
application design and implementation.
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 184
Types of Contention
Lock contention: waiting to access shared data
Latch contention: waiting to access Oracle internal
resources
– Can be difficult to diagnose properly, but there are classic
cases to avoid
I/O contention: waiting to read/write to disk
– Usually a DBA datafile / tablespace distribution issue
When user processes WAIT, algorithm efficiency
doesn’t matter at all!
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 185
SQL Locking Only one user can modify any
row in table
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 191
ORA-1555: Snapshot too old
Common cause:
– Batch update driven by cursor on large result set
– Other processes updating cursor table(s)
– Concurrency problem, not rollback segment sizing
False cure: commit more frequently
– Problem is NOT rollback for updates, but rollback for read-
consistent image of cursor (unaffected by commit)
True cure: close and re-open the driving cursor
– Process the large result set in a sequence of read-
consistent images
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 192
ORA-1555 Candidate
CURSOR big_result_cur IS
SELECT * FROM big_table;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 195
Synonyms add overhead
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 196
PL/SQL Tuning & Best Practices
Optimize Algorithms
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 197
Avoid Unnecessary Code
Execution
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 198
Avoid Repetitive Code Execution
A classic problem area:
PROCEDURE process_data (nm_in IN VARCHAR2) IS
BEGIN
FOR rec IN pkgd.cur
LOOP
process_rec (UPPER (nm_in), rec.total_production);
END LOOP;
END;
Easy solution:
PROCEDURE process_data (nm_in IN VARCHAR2) IS
v_nm some_table.some_column%TYPE := UPPER (nm_in);
BEGIN
FOR rec IN pkgd.cur
LOOP
process_rec (v_nm, rec.total_production);
END LOOP;
END;
effdsql.tst
loadlots*.*
Sometimes the problem is less obvious...
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 199
Optimal Collection Scanning Code
Still, this is far less than ideal as far as algorithms go. Why?
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 202
Optimal Table Scanning Code
Using the FIRST and NEXT methods, I don't have to worry
about table sparseness and I don't have to use EXISTS to
check my next row for existence
– NEXT returns NULL when you are at the highest row
– To scan in reverse, start with LAST and use PRIOR
rowind PLS_INTEGER := birthdays.FIRST;
BEGIN
LOOP
EXIT WHEN rowind IS NULL;
DBMS_OUTPUT.PUT_LINE plsqlloops.sp
(birthdays(rowind).best_present);
rowind := birthdays.NEXT (rowind);
END LOOP;
END;
slowalg_q2.sql
slowalg_a2.sql
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 204
Minimize Firing of Database Triggers
CREATE OR REPLACE TRIGGER check_raise
AFTER UPDATE OF salary, commission
ON employee FOR EACH ROW
WHEN (NVL (OLD.salary, -1) != NVL (NEW.salary, -1) OR
NVL (OLD.commission, -1) != NVL (NEW.commission, -1))
BEGIN
...
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 206
Do We Have Any Rows?
CREATE OR REPLACE PROCEDURE drop_dept
(deptno_in IN NUMBER, reassign_deptno_in IN NUMBER)
IS
temp_emp_count NUMBER;
BEGIN
-- Do we have any employees in this department to transfer?
SELECT COUNT(*)
INTO temp_emp_count
FROM emp WHERE deptno = deptno_in;
-- Reassign any employees
IF temp_emp_count >0
THEN
UPDATE emp
SET deptno = reassign_deptno_in
WHERE deptno = deptno_in;
END IF;
DELETE FROM dept WHERE deptno = deptno_in;
COMMIT;
END drop_dept;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 207
The Minimalist Approach
At least one row?
BEGIN
OPEN cur; Use an explicit cursor,
FETCH cur INTO rec; fetch once and then
IF cur%FOUND check the status.
THEN
...
atleastone.sql
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 208
Do Lots of Stuff At the Same Time
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 209
Parallelize Your Code with Pipes
Oracle uses DBMS_PIPE to improve RDBMS performance
– Parallel updates of indexes, partitioned queries, etc.
You can do the same for your application if:
– You have multiple CPUs available
– You have processes which can run in parallel; they mustn't be inter-
dependent
Start
parallel.sql
Synchronize
&
Continue
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 210
Schedule Asynchronous Execution
You can also use DBMS_JOB to execute programs in another
Oracle session
– The job can be run immediately, or at a specific time (and at a specific
interval. You will probably need to COMMIT to get the job going
– DBMS_JOB does not offer any inter-job relationships, so you have to
built in a pause for confirmation and so on
DECLARE
job# PLS_INTEGER;
PROCEDURE doit (job IN VARCHAR2) IS BEGIN
DBMS_JOB.SUBMIT (
job#, 'calc_comp (''' || job || ''')', SYSDATE, NULL);
COMMIT;
END;
BEGIN
doit ('CLERK');
doit ('VP');
doit ('PROGRAMMER');
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 211
Avoid the Heavy Lifting
For reasons of both efficiency and productivity, you should
always check to see if a solution is already available before
you build it yourself
– If Oracle wrote it, it is probably implemented in C, increasing the
likelihood that it will run faster than what you produce
Familiarize yourself with the built-in functionality, from
STANDARD-based functions to the built-in packages
Consider the following requirements:
– Return TRUE if a string is a valid number, false otherwise
– Search for the Nth occurrence of a substring within a string, and
return the rest of the string from that point isnum*.*
fromnth.*
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 212
Quiz!
Assuming that process_employee_history has been
optimized (a big assumption!), how else can we improve the
performance of this code?
DECLARE
CURSOR emp_cur
IS
SELECT last_name, TO_CHAR (SYSDATE, 'MM/DD/YYYY') today
FROM employee;
BEGIN
FOR rec IN emp_cur
LOOP
IF LENGTH (rec.last_name) > 20
THEN
rec.last_name := SUBSTR (rec.last_name, 1, 20);
END IF;
process_employee_history (rec.last_name, rec.today); slowalg_q1.sql
END LOOP; slowalg_a1.sql
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 213
Quiz!
Construct a schedule of lease payments for a store and save
them in a PL/ SQL table
– For each of 20 years, the lease amount is calculated as the sum of
lease amounts for the remaining years
– The total lease for year 10, in other words, would consist of the
lease amounts (fixed and variable) for years 10 through 20
Functions returning the lease amounts are provided
(dummies): pv_of_fixed, pv_of_variable
How best can we implement this requirement?
presvalue.sql
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 214
Quiz!
SQL> BEGIN
2 p.l (ltrim ('abcabcbadef', 'abc'));
3 p.l (lstrip ('abcabcbadef', 'abc', 2));
4 END;
5 /
def
badef
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 215
PL/SQL Tuning and Best
Practices
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 216
Use Data Structures Efficiently
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 217
When VARCHAR2 is not really
VARCHAR2
Before Oracle8, variables declared as VARCHAR2 were
treated computationally as variable length, but memory was
allocated as fixed-length. Beware!
On Oracle 7.3, you can use the following script to soak up all
the real memory available in your system:
DECLARE
TYPE big_type IS TABLE OF VARCHAR2(32767)
INDEX BY BINARY_INTEGER;
big big_type;
BEGIN
DBMS_OUTPUT.enable;
FOR i IN 1 .. 32767
LOOP
big (i) := NULL;
END LOOP;
END;
DECLARE DECLARE
str VARCHAR2(100); str VARCHAR2(100);
int PLS_INTEGER; int PLS_INTEGER;
BEGIN BEGIN
str := str || int; str := str || TO_CHAR (int);
implconv.tst
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 220
Minimize Transfer of Large
Structures
Default parameter passing in PL/SQL is by value, not reference
– This means that IN OUT parameters are copied into local data
structures, upon which changes are made. The local contents are then
copied back to the parameter upon successful completion
Large IN OUT parameters (records and collections, primarily)
can cause performance degradation
Two solutions to this problem:
– Move data structure out of parameter list and into a package
Specification (making it a "global")
– Use the NOCOPY feature of Oracle8i
You can also use the Oracle8i UTL_COLL to tune access to
nested tables
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 221
Move Structure to Package Spec.
You can avoid passing a large structure by defining it in the
specification of a package, making it globally accessible
– You can then remove it from the parameter list of your program and
reference it directly within the program
– SAFEST way to do this is to put the collection or record itself in the
package body and provide programs to access that structure
– Note: performance gains will vary greatly depending on what kind of
operations you perform on the structure
When you do this, you lose some flexibility
– Only once instance of the global can be manipulated within your
session
– This is quite different from passing any number of different structures
as parameters
pkgvar.pkg
pkgvar.tst
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 222
Use the NOCOPY Feature of Oracle8i
PROCEDURE nocopydemo (
some_collection IN OUT NOCOPY some_collection_tabtype,
some_object OUT NOCOPY some_object_type);
materializing a large
FROM TABLE(CAST(petlist AS Pettab_t)) l
WHERE l.name LIKE like_str)
locator
END LOOP;
ELSE
FOR i IN 1..petlist.COUNT
– Handy when you only LOOP
IF petlist(i).name LIKE like_str
want to access a portion THEN
of the collection's
list_to_return.EXTEND;
list_to_return(i) := petlist(i);
attributes END IF;
END LOOP;
END IF;
utlcoll*.sql RETURN list_to_return;
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 225
Avoid NOT NULL Constraint
Oracle recommends that you avoid applying the NOT NULL constraint
to a declaration, and instead check for a NULL value yourself
Instead of this:
DECLARE
my_value INTEGER NOT NULL := 0;
BEGIN
IF my_value > 0 THEN ...
END;
Do this:
DECLARE
my_value INTEGER := 0;
BEGIN
IF my_value IS NULL THEN /* ERROR! */
ELSIF my_value > 0 THEN ...
END;
notnull.tst
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 227
Cache Data with Packages
General principle: if you need to access the same data and it
doesn't change, cache it in the most accessible location to
maximize lookup performance
Packages offer an ideal caching mechanism
– Any data structure defined at the package level (whether in
specification or body) serves as a persistent, global structure
– Remember: separate copy for each connection to Oracle
Let's explore further:
– Define, protect and access package data
– Use the initialization section
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 228
Define, Access and Protect Pkg Data
Great example: the USER function
– The value returned by USER never changes in a session
– Each call to USER is in reality a SELECT FROM dual
– So why do it more than once?
CREATE OR REPLACE PACKAGE thisuser
IS
Hide package data!
FUNCTION name RETURN VARCHAR2; If exposed, you cannot
END;
guarantee integrity of data.
CREATE OR REPLACE PACKAGE BODY thisuser Build "get and set"
IS
/* Persistent "global" variable */ programs around it.
g_user VARCHAR2(30) := USER;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 230
Data Caching with PL/SQL Tables
Data retrieved
First access Pass Data from cache Data returned
to Cache to application
Database Application
Function
Not in cache; PGA
Request data Application
from database Requests Data
Data found in
cache. Database Application
Database is not needed.
Function
PGA
Application
Requests Data
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 231
Code for a Caching Function
PACKAGE te_company Specification
IS
FUNCTION name$val (id_in IN company.company_id%TYPE)
RETURN company.name%TYPE;
END te_company;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 232
Body of Self-Optimizing Function
FUNCTION name$val (id_in IN company.company_id%TYPE)
RETURN company.name%TYPE
IS
CURSOR comp_cur IS
SELECT name FROM company where company_id = id_in;
retval company.name%TYPE;
BEGIN
IF names.EXISTS (id_in) If the row is cached,
THEN retrieve from cache.
retval := names (id_in);
ELSE
OPEN comp_cur; FETCH comp_cur INTO retval;
CLOSE comp_cur;
If not in cache, use
IF retval IS NOT NULL standard database
THEN retrieval.
names (id_in) := retval;
END IF;
END IF;
RETURN retval; emplu.pkg
END name$val; If a match was found emplu.tst
END te_company; store it in the
cache for next time.
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 233
Another Tool, To Be Applied
Selectively
Local caching isn't always appropriate
– The data should be static. If the table from which you cached data
changes, it is very difficult to update the cache
– Either for small tables or for large tables with "hot spots"
Provide the string, the base or starting point, and the hash size (total
number of possible return values).
Tips for hashing:
– You must use the same base and hash size to obtain consistent hash
values
– Maximum hash size: upper limit of BINARY_INTEGER: 2**31-1
– No guarantee that two different strings will not hash to the same
number. Check for and resolve conflicts
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 235
A Hash Encapsulator
Hide base and hash table values to ensure consistency
CREATE OR REPLACE PACKAGE hash
IS
FUNCTION val (str IN VARCHAR2) RETURN NUMBER;
END hash;
/
CREATE OR REPLACE PACKAGE BODY hash
IS
maxRange CONSTANT BINARY_INTEGER := POWER (2, 31) - 1;
strt CONSTANT BINARY_INTEGER := 2;
hash.pkg
hashcomp.tst
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 236
Hashing for an Alternative Index
Index-by tables support only one index: the row number
– So to locate the row in which a particular string is located, you have
to do a "full table scan" -- or do you?
Use the hash function to build an alternative index to the
contents of the PL/SQL table
Hash Name to
Produce Alt Index Row # Alternate Index Collection
Cached Data Collection
Row # is Complete the round trip to find the row for a name
employee ID
number. altind*.pkg
altind.tst
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 237
Quiz! slowds*.sql
How can we
CREATE OR REPLACE PROCEDURE fix_me (
nmfilter IN VARCHAR2, comm_list IN OUT comm_pkg.reward_tt
)
tune up this IS
v_nmfilter VARCHAR2(2000) NOT NULL := nmfilter;
program?
v_info VARCHAR2(2000);
v_nth INTEGER;
v_sal NUMBER;
Remember: You
indx INTEGER
BEGIN
FOR indx IN comm_list.FIRST .. comm_list.LAST
will not usually LOOP
v_nth := v_nth + 1;
UPDATE employee
cumulative effect SET salary := comm_list(indx).sal * 2.0,
info := v_info,
impressive.
comm_list(indx).comm := 0;
END IF;
END LOOP;
END;
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 238
PL/SQL Tuning and Best
Practices
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 239
Manage Code in the
Database/SGA
PL/SQL code is stored in the Oracle data dictionary
and executed from the SGA's shared pool area
– It is imperative that you understand the PL/SQL
architecture and then tune access to your code
PL/SQL Architecture in Shared Memory
Analyze Code Usage & Dependencies
Manage Memory Allocation and Usage
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 240
Analyze Code Usage &
Dependencies
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 241
How Big is My Code?
Oracle offers the *_OBJECT_SIZE views to give you information
about the size of your code. There are three different entries of
interest:
– Source size: the source code, used to recompile the program
– Parsed size: Code required to compile other programs that reference this
one. If you "keep" a program unit, this code is also loaded into the SGA
– Code size: partially-compiled code that is loaded into the SGA
Use the view to identify large programs and compare sizes of
different implementations
Large program units are generally good candidates for pinning into
the SGA (covered later)
pssize.sql
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 242
What Does My Code
Need to Run?
If program A calls program B, then both A and B need to be
present in the SGA for A to execute
Oracle maintains dependency information in a variety of
views (*_DEPENDENCIES and PUBLIC_DEPENDENCY)
Analyze complete memory requirements!
– Combine dependency information with USER_OBJECT_SIZE to
get a more complete picture
– Or take snapshots (before and after) of what is in the SGA (more
on this later)
Watch out! Accessing these DD views can be very time-
consuming (to learn and to execute). analyzedep.ins
analyzedep.sql
analyzedep.tst
utldtree.sql
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 243
What is Cached in the SGA?
The best way to understand the requirements and activity of
the PL/SQL code in the SGA is to look at the SGA
Oracle offers a variety of data structures to get this
information:
– V$ROWCACHE: check for data dictionary cache hits/misses
– V$LIBRARYCACHE: check for object access hits/misses
– V$SQLAREA: statistics on shared SQL area, one row per SQL
string (cursor or PL/SQL block)
– V$DB_OBJECT_CACHE: displays info on database objects that
are cached in the library cache
And much more. We will just scratch the surface and pull out
those items most useful for PL/SQL tuning
grantv$.sql
insga.pkg
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 244
Library Cache views
Shared Pool
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 245
The V$DB_OBJECT_CACHE View
The V$DB_OBJECT_CACHE view gives you this
information:
– LOADS: number of times element has been loaded to the SGA
– EXECUTIONS: number of times element has been executed. This
is not aggregated for the program name. Only for the code block.
– KEPT: YES if the element has been pinned
– TYPE: NOT LOADED if the element has been referenced but not
loaded
Excessive numbers of loads and/or high execution counts
indicate the need for adjusting the shared pool size and/or
pinning that program unit
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 246
The V$SQLAREA View
insga.pkg
similar.sql
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 247
Tune Memory Allocation
and Usage
Set the SGA shared pool size
Manage session memory with DBMS_SESSION
Use the SERIALLY_REUSABLE Pragma
Minimize program inter-dependencies
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 248
Size the Shared Pool
The shared pool must be large enough to cache your code
without excessive (or any) swapping
– Oracle uses the least-recently-used algorithm to make room for code
that needs to be executed
– Tune size of shared pool through analysis of contents of shared pool
when application is up and running
INIT.ORA parameters of interest:
– SHARED_POOL_SIZE: size in bytes of shared pool area
– SHARED_POOL_RESERVED_SIZE: size in bytes of portion of shared
pool area reserved for requests larger than
SHARED_POOL_MIN_ALLOCATION when the shared pool does not
have enough contiguous blocks for request.
SQL> exec insga.show_hitratio
ROW CACHE Hit ratio = 93.81%
Ratio is below 95%. Consider raising the SHARED_POOL_SIZE init.ora
parameter.
LIBRARY CACHE Hit
9/16/2006 ratio
Copyright = 99.61%
2001 Steven Feuerstein, PL/SQL Best Practices - page 249
Manage Session Memory
DBMS_SESSION.RESET_PACKAGE
– Reinitializes all package states, your own and even the built-in
packages like DBMS_OUTPUT
– Never use in stored PL/SQL (embedded within an application);
package states will not be re-instantiated until outer-most PL/SQL
block within which RESET_PACKAGE was called completes
– RESET_PACKAGE does not release memory associated with cleared
package variables (see next program)
DBMS_SESSION.FREE_UNUSED_USER_MEMORY
– Returns memory back to OS or shared pool
– Call after compilation of large objects, when large PL/SQL tables are
no longer needed, or after large sort operations
Recommendation: clear out memory before you start a profiling
session to "make all things equal" reset.sql
resetmem.sql
mysess.pkg
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 250
Freeing Unused User Memory
(FUUM)
After procedure
execution and
FUUM, no
memory
allocated.
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 251
Pin Code in the SGA
"Pin" your code (including triggers and types) into shared
memory, and exempt it from the LRU algorithm
– When you pin using, your code remains in the SGA until the instance is
shutdown
Pinning is usually performed right after database startup. Prior to
Oracle 7.3, two separate steps were required
– 1. Register that program, package or cursor to be pinned by calling the
DBMS_SHARED_POOL.KEEP procedure
– 2. Reference the code element so that it will be loaded into memory.
– This second step is now performed with the call to KEEP
The DBMS_SHARED_POOL is not automatically
installed/available in all versions. See pool.ins.
pool.ins
plvpin.sp
keeper.sql
showkeep.sql
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 252
Use the SERIALLY_REUSABLE Pragma
Proc 1 Proc 2
Proc 1
Old
New
analyzedep.*
utldtree.sql
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 254
PL/SQL Tuning & Best Practices
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 255
Writing Code You Can Be Proud Of
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 256
Use Everything the Language Offers
PL/SQL is based on Ada, and is a highly structured and
robust language. Use it, all of it! Some examples...
If a variable's value doesn't change, declare a constant.
This both protects the value and also documents for
others that it should not be changed.
Use labels for anonymous blocks and loops.
They're not just or GOTOs.
c_max_date CONSTANT DATE DEFAULT SYSDATE;
<<every_month>>
FOR month_ind IN 1 .. 12
LOOP
every_month.month_ind ...
END LOOP every_month;
IS
valid_company CHAR(1);
Nasty!
BEGIN
You don't have to use Y IF valid_company = 'Y'
or N, T or F, etc. to
represent these actual
values. IS Better...
valid_company BOOLEAN;
BEGIN
IF valid_company = TRUE
IS
valid_company BOOLEAN; Best.
BEGIN
IF valid_company
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 258
Achieving a Consistent Coding Style
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 261
The Name is a Lot of the Game
Both the structure and content of the names you give to
program elements have a direct impact on the quality of your
code
Potential problems include:
– The name does not accurately describe what the code element does
or represents
– The same code element is represented by a bewildering array of
different names
– The structure of the name misrepresents the code; usually this occurs
when you give "procedure names" to functions
– Names of database objects, such as tables and columns, are used as
names of PL/SQL variables
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 262
Recommendations for Naming
Elements
Use standard prefixes or Cursor company_cur
suffixes for code elements Record company_rec
Local variable l_ename
Most important aspect Global variable g_ename
of conventions: Constant c_lastdate
PL/SQL Table Type dates_tabtype
Consistent application!
PL/SQL Table dates_tab
IF customer_flag.discount = constants.ineligible
THEN
Better names for variables, named constants got rid of the comment
Check out
General coding guidelines Code Complete
by Steve McConnell
– All naming conventions are followed for many more
– No hard-coded values coding tips and
checklists.
– Repetitive code is modularized
– Functions always return values
– One way in, one way out: loops and programs
– Unused variables and code sections are removed
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 266
Organizing Source Code in Files
Use standard file extensions for different kinds of source code:
pks for package specs, pkb for package bodies, etc.
Always separate the "CREATE OR REPLACE" for package
specifications and bodies into different files
– You only recompile your package specifications when needed
(minimizing the cascade of INVALID program units)
Create/compile all of your specifications before you compile
your bodies
– This defines all the headers or stubs for your application code to
compile
– You can then create packages with inter-dependencies and no
compilation problems
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 267
Creating Readable/Maintainable Code
Summary
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 268
You Can Write Blazing Fast
PL/SQL!
Tuning PL/SQL code is an iterative and incremental process
– You are unlikely to uncover a "silver bullet" that is not related to some
SQL statement. You can, however, have a substantial impact on the
performance of your and others' code
Write code with efficiency in mind, but save intensive tuning until
entire components are complete and you can perform
benchmarking
MOST IMPORTANT! Avoid repetition and dispersion of SQL
statements throughout your application
PL/SQL code is executed from shared memory. You must tune the
shared pool to avoid excessive swapping of code
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 269
Closing Comments
9/16/2006 Copyright 2001 Steven Feuerstein, PL/SQL Best Practices - page 270