Oracle Database Performance Tips
Oracle Database Performance Tips
Prepared By
Ayman Mohammed EL Moniary
1
Contents
Oracle Database Performance Tips ................................................................................................................1
Partitioning ...............................................................................................................................................................5
Partitioning Key.................................................................................................................................................................................... 5
Partitioned Tables ................................................................................................................................................................................. 5
When to Partition a Table ..................................................................................................................................................................... 6
When to Partition an Index ................................................................................................................................................................... 6
Benefits of Partitioning ......................................................................................................................................................................... 6
Partitioning Strategies........................................................................................................................................................................... 6
Single-Level Partitioning ...................................................................................................................................................................... 7
Range Partitioning ............................................................................................................................................................................ 7
Hash Partitioning .............................................................................................................................................................................. 7
List Partitioning ................................................................................................................................................................................ 7
Composite Partitioning ......................................................................................................................................................................... 8
Composite Range-Range Partitioning .............................................................................................................................................. 8
Composite Range-Hash Partitioning ................................................................................................................................................ 8
Composite Range-List Partitioning .................................................................................................................................................. 8
Composite List-Range Partitioning .................................................................................................................................................. 8
Composite List-Hash Partitioning .................................................................................................................................................... 8
Composite List-List Partitioning ...................................................................................................................................................... 8
Interval Partitioning .............................................................................................................................................................................. 9
Overview of Partitioned Indexes .......................................................................................................................................................... 9
Local Partitioned Indexes ................................................................................................................................................................. 9
Global Partitioned Indexes ............................................................................................................................................................. 10
2
Deallocating Unused Space ............................................................................................................................................................ 20
Dropping Unused Object Storage................................................................................................................................................... 20
BULK COLLECT Clause .................................................................................................................................................................. 21
3
Stop avoiding bulk binds .................................................................................................................................................................... 57
Stop using pass-by-value (NOCOPY) .................................................................................................................................................. 58
Stop using the wrong data types ......................................................................................................................................................... 58
Quick Points ....................................................................................................................................................................................... 58
4
Partitioning
Partitioning allows a table, index, or index-organized table to be subdivided into smaller pieces, where each piece of such a database
object is called a partition. Each partition has its own name, and may optionally have its own storage characteristics.
From the perspective of a database administrator, a partitioned object has multiple pieces that can be managed either collectively or
individually. This gives the administrator considerable flexibility in managing partitioned objects. However, from the perspective of
the application, a partitioned table is identical to a non-partitioned table; no modifications are necessary when accessing a partitioned
table using SQL queries and DML statements.
The following picture offers a graphical view of how partitioned tables differ from non-partitioned tables.
Note:
All partitions of a partitioned object must reside in tablespaces of a single block size.
Partitioning Key
Each row in a partitioned table is unambiguously assigned to a single partition. The partitioning key is comprised of one or more
columns that determine the partition where each row will be stored. Oracle automatically directs insert, update, and delete operations
to the appropriate partition through the use of the partitioning key
Partitioned Tables
Any table can be partitioned into a million separate partitions except those tables containing columns with LONG or LONG RAW
datatypes. You can, however, use tables containing columns with CLOB or BLOB datatypes.
Note:
To reduce disk usage and memory usage (specifically, the buffer cache), you can store tables and partitions of a partitioned table in a
compressed format inside the database. This often leads to a better scale up for read-only operations. Table compression can also
speed up query execution. There is, however, a slight cost in CPU overhead.
5
Table compression should be used with highly redundant data, such as tables with many foreign keys. You should avoid compressing
tables with much update or other DML activity. Although compressed tables or partitions are updatable, there is some overhead in
updating these tables, and high update activity may work against compression by causing some space to be wasted.
Benefits of Partitioning
Partitioning can provide tremendous benefit to a wide variety of applications by improving performance, manageability, and
availability. It is not unusual for partitioning to improve the performance of certain queries or maintenance operations by an order of
magnitude. Moreover, partitioning can greatly simplify common administration tasks.
Partitioning also enables database designers and administrators to tackle some of the toughest problems posed by cutting-edge
applications. Partitioning is a key tool for building multi-terabyte systems or systems with extremely high availability requirements.
Partitioning Strategies
Oracle Partitioning offers three fundamental data distribution methods as basic partitioning strategies that control how data is placed
into individual partitions:
Range
Hash
List
Using these data distribution methods, a table can either be partitioned as a single list or as a composite partitioned table:
Single-Level Partitioning
Composite Partitioning
Each partitioning strategy has different advantages and design considerations. Thus, each strategy is more appropriate for a particular
situation.
6
Single-Level Partitioning
A table is defined by specifying one of the following data distribution methodologies, using one or more columns as the partitioning
key:
Range Partitioning
Hash Partitioning
List Partitioning
Range Partitioning
Range partitioning maps data to partitions based on ranges of values of the partitioning key that you establish for each partition. It is
the most common type of partitioning and is often used with dates. For a table with a date column as the partitioning key, the January-
2005 partition would contain rows with partitioning key values from 01-Jan-2005 to 31-Jan-2005.
Each partition has a VALUES LESS THAN clause, which specifies a non-inclusive upper bound for the partitions. Any values of the
partitioning key equal to or higher than this literal are added to the next higher partition. All partitions, except the first, have an
implicit lower bound specified by the VALUES LESS THAN clause of the previous partition.
A MAXVALUE literal can be defined for the highest partition. MAXVALUE represents a virtual infinite value that sorts higher than
any other possible value for the partitioning key, including the NULL value.
Hash Partitioning
Hash partitioning maps data to partitions based on a hashing algorithm that Oracle applies to the partitioning key that you identify.
The hashing algorithm evenly distributes rows among partitions, giving partitions approximately the same size.
Hash partitioning is the ideal method for distributing data evenly across devices. Hash partitioning is also an easy-to-use alternative to
range partitioning, especially when the data to be partitioned is not historical or has no obvious partitioning key.
List Partitioning
List partitioning enables you to explicitly control how rows map to partitions by specifying a list of discrete values for the partitioning
key in the description for each partition. The advantage of list partitioning is that you can group and organize unordered and unrelated
sets of data in a natural way. For a table with a region column as the partitioning key, the North America partition might contain
values Canada, USA, and Mexico.
The DEFAULT partition enables you to avoid specifying all possible values for a list-partitioned table by using a default partition, so
that all rows that do not map to any other partition do not generate an error.
7
Composite Partitioning
Composite partitioning is a combination of the basic data distribution methods; a table is partitioned by one data distribution method
and then each partition is further subdivided into subpartitions using a second data distribution method. All subpartitions for a given
partition together represent a logical subset of the data.
Composite partitioning supports historical operations, such as adding new range partitions, but also provides higher degrees of
potential partition pruning and finer granularity of data placement through subpartitioning
8
Interval Partitioning
Interval partitioning is an extension of range partitioning which instructs the database to automatically create partitions of a specified
interval when data inserted into the table exceeds all of the existing range partitions. You must specify at least one range partition.
The range partitioning key value determines the high value of the range partitions, which is called the transition point, and the
database creates interval partitions for data beyond that transition point. The lower boundary of every interval partition is the non-
inclusive upper boundary of the previous range or interval partition.
When using interval partitioning, consider the following restrictions:
You can only specify one partitioning key column, and it must be of NUMBER or DATE type.
Interval partitioning is not supported for index-organized tables
9
Global Partitioned Indexes
Oracle offers two types of global partitioned indexes: range partitioned and hash partitioned.
10
Global Partitioned Indexes
11
Oracle Bulk Operations
Without the bulk bind, PL/SQL sends a SQL statement to the SQL engine for each record that is inserted, updated, or deleted leading
to context switches that hurt performance
FORALL Statement
The FORALL statement runs one DML statement multiple times, with different values in the VALUES and WHERE clauses. The
different values come from existing, populated collections or host arrays. The FORALL statement is usually much faster than an
equivalent FOR LOOP statement.
Note:
You can use the FORALL statement only in server programs, not in client programs.
Example DELETE Statement in FORALL Statement
DECLARE
TYPE NumList IS VARRAY(20) OF NUMBER;
depts NumList := NumList(10, 30, 70); -- department numbers
BEGIN
FORALL i IN depts.FIRST..depts.LAST
DELETE FROM employees_temp
WHERE department_id = depts(i);
END;
Time Difference for INSERT Statement in FOR LOOP and FORALL Statement
DROP TABLE parts1;
CREATE TABLE parts1 (
pnum INTEGER,
pname VARCHAR2(15)
);
DROP TABLE parts2;
CREATE TABLE parts2 (
pnum INTEGER,
pname VARCHAR2(15)
);
DECLARE
TYPE NumTab IS TABLE OF parts1.pnum%TYPE INDEX BY PLS_INTEGER;
TYPE NameTab IS TABLE OF parts1.pname%TYPE INDEX BY PLS_INTEGER;
pnums NumTab;
pnames NameTab;
iterations CONSTANT PLS_INTEGER := 50000;
t1 INTEGER;
t2 INTEGER;
t3 INTEGER;
BEGIN
FOR j IN 1..iterations LOOP -- populate collections
pnums(j) := j;
pnames(j) := 'Part No. ' || TO_CHAR(j);
END LOOP;
t1 := DBMS_UTILITY.get_time;
FOR i IN 1..iterations LOOP
INSERT INTO parts1 (pnum, pname)
12
VALUES (pnums(i), pnames(i));
END LOOP;
t2 := DBMS_UTILITY.get_time;
FORALL i IN 1..iterations
INSERT INTO parts2 (pnum, pname)
VALUES (pnums(i), pnames(i));
t3 := DBMS_UTILITY.get_time;
DBMS_OUTPUT.PUT_LINE('Execution Time (secs)');
DBMS_OUTPUT.PUT_LINE('---------------------');
DBMS_OUTPUT.PUT_LINE('FOR LOOP: ' || TO_CHAR((t2 - t1)/100));
DBMS_OUTPUT.PUT_LINE('FORALL: ' || TO_CHAR((t3 - t2)/100));
COMMIT;
END;
/
Result is similar to:
Execution Time (secs)
---------------------
FOR LOOP: 5.97
FORALL: .07
PL/SQL procedure successfully completed.
13
END LOOP;
COMMIT; -- Commit results of successful updates
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Unrecognized error.');
RAISE;
END;
/
14
-- Count how many rows were inserted for each department; that is,
-- how many employees are in each department.
DBMS_OUTPUT.PUT_LINE (
'Dept '||deptnums(i)||': inserted '||
SQL%BULK_ROWCOUNT(i)||' records'
);
END LOOP;
DBMS_OUTPUT.PUT_LINE('Total records inserted: ' || SQL%ROWCOUNT);
END;
/
Result:
Dept 10: inserted 1 records
Dept 20: inserted 2 records
Dept 30: inserted 6 records
Dept 40: inserted 1 records
Dept 50: inserted 45 records
Dept 60: inserted 5 records
Dept 70: inserted 1 records
Dept 80: inserted 34 records
Dept 90: inserted 3 records
Dept 100: inserted 6 records
Dept 110: inserted 2 records
Dept 120: inserted 0 records
Dept 130: inserted 0 records
Dept 140: inserted 0 records
Dept 150: inserted 0 records
Dept 160: inserted 0 records
Dept 170: inserted 0 records
Dept 180: inserted 0 records
Dept 190: inserted 0 records
Dept 200: inserted 0 records
Dept 210: inserted 0 records
Dept 220: inserted 0 records
Dept 230: inserted 0 records
Dept 240: inserted 0 records
Dept 250: inserted 0 records
Dept 260: inserted 0 records
Dept 270: inserted 0 records
Dept 280: inserted 0 records
Total records inserted: 106
APPEND_VALUES Hint
We have been able take advantage of the performance benefits of direct-path inserts in "INSERT ... SELECT" operations for a long
time using the APPEND Hint.
INSERT /*+ APPEND */ INTO dest_tab SELECT * FROM source_tab;
The APPEND_VALUES hint in Oracle 11g Release 2 now allows us to take advantage of direct-path inserts when insert statements
include a VALUES clause. Typically we would only want to do this when the insert statement is part of bulk operation using the
FORALL statement. We will use the following table to demonstrate the effect of the hint.
CREATE TABLE forall_test (
id NUMBER(10),
15
code VARCHAR2(10),
description VARCHAR2(50)
);
ALTER TABLE forall_test ADD (CONSTRAINT forall_test_pk PRIMARY KEY (id));
ALTER TABLE forall_test ADD (CONSTRAINT forall_test_uk UNIQUE (code));
The following code populates the base table then deletes half of the rows before performing each test. This is because during a regular
(conventional-path) insert, Oracle tries to use up any free space currently allocated to the table, including space left from previous
delete operations. In contrast direct-path inserts ignore existing free space and append the data to the end of the table. After preparing
the base table we time how long it takes to perform conventional-path insert as part of the FORALL statement. Next, we repeat the
same test, but this time use a the APPEND_VALUES hint to give us direct-path inserts.
SET SERVEROUTPUT ON
DECLARE
TYPE t_forall_test_tab IS TABLE OF forall_test%ROWTYPE;
l_tab t_forall_test_tab := t_forall_test_tab();
l_start NUMBER;
l_size NUMBER := 1000000;
PROCEDURE prepare_table AS
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE forall_test';
INSERT /*+ APPEND */ INTO forall_test
SELECT level, TO_CHAR(level), 'Description: ' || TO_CHAR(level)
FROM dual
CONNECT BY level <= l_size;
COMMIT;
DELETE FROM forall_test WHERE MOD(id, 2) = 0;
COMMIT;
END prepare_table;
BEGIN
-- Populate collection.
FOR i IN 1 .. (l_size/2) LOOP
l_tab.extend;
l_tab(l_tab.last).id := i*2;
l_tab(l_tab.last).code := TO_CHAR(i*2);
l_tab(l_tab.last).description := 'Description: ' || TO_CHAR(i*2);
END LOOP;
prepare_table;
-- ----------------------------------------------------------------
-- Test 1: Time bulk inserts.
l_start := DBMS_UTILITY.get_time;
FORALL i IN l_tab.first .. l_tab.last
INSERT INTO forall_test VALUES l_tab(i);
DBMS_OUTPUT.put_line('Bulk Inserts : ' ||
(DBMS_UTILITY.get_time - l_start));
-- ----------------------------------------------------------------
ROLLBACK;
prepare_table;
-- ----------------------------------------------------------------
-- Test 2: Time bulk inserts using the APPEND_VALUES hint.
l_start := DBMS_UTILITY.get_time;
FORALL i IN l_tab.first .. l_tab.last
INSERT /*+ APPEND_VALUES */ INTO forall_test VALUES l_tab(i);
DBMS_OUTPUT.put_line('Bulk Inserts /*+ APPEND_VALUES */ : ' ||
16
(DBMS_UTILITY.get_time - l_start));
-- ----------------------------------------------------------------
ROLLBACK;
END;
/
Bulk Inserts : 394
Bulk Inserts /*+ APPEND_VALUES */ : 267
PL/SQL procedure successfully completed.
SQL>
We can see that the APPEND_VALUES hint gives us better performance by allowing us to use direct-path inserts within the
FORALL statement. Remember there are factors other than performance to consider before deciding to use direct-path inserts. Also,
this hint does not currently work with the SAVE EXCEPTIONS clause. If you try to use them together you will get the following
error.
ORA-38910: BATCH ERROR mode is not supported for this operation
Oracle Database inserts data into a table in one of two ways:
17
Serial Mode Inserts with SQL Statements
You can activate direct-path insert in serial mode with SQL in the following ways:
If you are performing an INSERT with a subquery, specify the APPEND hint in each INSERT statement, either immediately after the
INSERT keyword, or immediately after the SELECT keyword in the subquery of the INSERT statement.
If you are performing an INSERT with the VALUES clause, specify the APPEND_VALUES hint in each INSERT statement
immediately after the INSERT keyword. Direct-path insert with the VALUES clause is best used when there are hundreds of
thousands or millions of rows to load. The typical usage scenario is for array inserts using OCI. Another usage scenario might be
inserts in a FORALL loop in PL/SQL.
If you specify the APPEND hint (as opposed to the APPEND_VALUES hint) in an INSERT statement with a VALUES clause, the
APPEND hint is ignored and a conventional insert is performed.
Note:
You cannot query or modify direct-path inserted data immediately after the insert is complete. If you attempt to do so, an ORA-12838
error is generated. You must first issue a COMMIT statement before attempting to read or modify the newly-inserted data.
Note:
If the database or tablespace is in FORCE LOGGING mode, then direct path INSERT always logs, regardless of the logging setting.
18
Beginning with release 11.2.0.2 of Oracle Database, you can significantly improve the performance of unrecoverable direct path
inserts by disabling the periodic update of the controlfiles. You do so by setting the initialization parameter
DB_UNRECOVERABLE_SCN_TRACKING to FALSE. However, if you perform an unrecoverable direct path insert with these
controlfile updates disabled, you will no longer be able to accurately query the database to determine if any datafiles are currently
unrecoverable.
Additional Considerations for Direct-Path INSERT
The following are some additional considerations when using direct-path INSERT.
Compressed Tables
If a table is created with the basic compression, then you must use direct-path INSERT to compress table data as it is loaded. If a table
is created with OLTP, warehouse, or online archival compression, then best compression ratios are achieved with direct-path insert.
Index Maintenance with Direct-Path INSERT
Oracle Database performs index maintenance at the end of direct-path INSERT operations on tables (partitioned or non-partitioned)
that have indexes. This index maintenance is performed by the parallel execution servers for parallel direct-path INSERT or by the
single process for serial direct-path INSERT. You can avoid the performance impact of index maintenance by making the index
unusable before the INSERT operation and then rebuilding it afterward.
Space Considerations with Direct-Path INSERT
Direct-path INSERT requires more space than conventional-path INSERT.
All serial direct-path INSERT operations, as well as parallel direct-path INSERT into partitioned tables, insert data above the high-
water mark of the affected segment. This requires some additional space.
Parallel direct-path INSERT into non-partitioned tables requires even more space, because it creates a temporary segment for each
degree of parallelism. If the non-partitioned table is not in a locally managed tablespace in automatic segment-space management
mode, you can modify the values of the NEXT and PCTINCREASE storage parameter and MINIMUM EXTENT tablespace
parameter to provide sufficient (but not excess) storage for the temporary segments. Choose values for these parameters so that:
The size of each extent is not too small (no less than 1 MB). This setting affects the total number of extents in the object.
The size of each extent is not so large that the parallel INSERT results in wasted space on segments that are larger than necessary.
After the direct-path INSERT operation is complete, you can reset these parameters to settings more appropriate for serial operations.
Locking Considerations with Direct-Path INSERT
During direct-path INSERT, the database obtains exclusive locks on the table (or on all partitions of a partitioned table). As a result,
users cannot perform any concurrent insert, update, or delete operations on the table, and concurrent index creation and build
operations are not permitted. Concurrent queries, however, are supported, but the query will return only the information before the
insert operation.
19
the space until a future time. This option is useful if you have long-running queries that might span the operation and attempt to read
from blocks that have been reclaimed. The defragmentation and compaction results are saved to disk, so the data movement does not
have to be redone during the second phase. You can reissue the SHRINK SPACE clause without the COMPACT clause during off-
peak hours to complete the second phase.
The CASCADE clause extends the segment shrink operation to all dependent segments of the object. For example, if you specify
CASCADE when shrinking a table segment, all indexes of the table will also be shrunk. (You need not specify CASCADE to shrink
the partitions of a partitioned table.) To see a list of dependent segments of a given object, you can run the
OBJECT_DEPENDENT_SEGMENTS procedure of the DBMS_SPACE package.
As with other DDL operations, segment shrink causes subsequent SQL statements to be reparsed because of invalidation of cursors
unless you specify the COMPACT clause.
Examples
Shrink a table and all of its dependent segments (including BASICFILE LOB segments):
ALTER TABLE employees SHRINK SPACE CASCADE;
Shrink a BASICFILE LOB segment only:
ALTER TABLE employees MODIFY LOB (perf_review) (SHRINK SPACE);
Shrink a single partition of a partitioned table:
ALTER TABLE customers MODIFY PARTITION cust_P1 SHRINK SPACE;
Shrink an IOT index segment and the overflow segment:
ALTER TABLE cities SHRINK SPACE CASCADE;
Shrink an IOT overflow segment only:
ALTER TABLE cities OVERFLOW SHRINK SPACE
20
BULK COLLECT Clause
The BULK COLLECT clause, a feature of bulk SQL, returns results from SQL to PL/SQL in batches rather than one at a time. The
BULK COLLECT clause can appear in:
SELECT INTO statement
FETCH statement
RETURNING INTO clause of:
DELETE statement
INSERT statement
UPDATE statement
EXECUTE IMMEDIATE statement
With the BULK COLLECT clause, each of the preceding statements retrieves an entire result set and stores it in one or more
collection variables in a single operation (which is more efficient than using a loop statement to retrieve one result row at a time).
Note:
PL/SQL processes the BULK COLLECT clause similar to the way it processes a FETCH statement inside a LOOP statement.
PL/SQL does not raise an exception when a statement with a BULK COLLECT clause returns no rows. You must check the target
collections for emptiness
21
Employee #100: King
Employee #101: Kochhar
Employee #102: De Haan
Employee #103: Hunold
Employee #104: Ernst
Employee #105: Austin
Example Limiting Bulk Selection with ROWNUM, SAMPLE, and FETCH FIRST
DECLARE
TYPE SalList IS TABLE OF employees.salary%TYPE;
sals SalList;
BEGIN
SELECT salary BULK COLLECT INTO sals FROM employees
WHERE ROWNUM <= 50;
SELECT salary BULK COLLECT INTO sals FROM employees
SAMPLE (10);
SELECT salary BULK COLLECT INTO sals FROM employees
FETCH FIRST 50 ROWS ONLY;
END;
22
Example Limiting Bulk FETCH with LIMIT
DECLARE
TYPE numtab IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
CURSOR c1 IS
SELECT employee_id
FROM employees
WHERE department_id = 80
ORDER BY employee_id;
empids numtab;
BEGIN
OPEN c1;
LOOP -- Fetch 10 rows or fewer in each iteration
FETCH c1 BULK COLLECT INTO empids LIMIT 10;
DBMS_OUTPUT.PUT_LINE ('------- Results from One Bulk Fetch --------');
FOR i IN 1..empids.COUNT LOOP
DBMS_OUTPUT.PUT_LINE ('Employee Id: ' || empids(i));
END LOOP;
EXIT WHEN c1%NOTFOUND;
END LOOP;
CLOSE c1;
END;
23
depts NumList := NumList(10,20,30);
Note:
You cannot run a pipelined table function over a database link. The reason is that the return type of a pipelined table function is a SQL
user-defined type, which can be used only in a single database. Although the return type of a pipelined table function might appear to
be a PL/SQL type, the database actually converts that PL/SQL type to a corresponding SQL user-defined type.
24
Result pls_integer;
BEGIN
For I in v_from..v_to LOOP
pls_tab(I):=I;
END LOOP:
SELECT count(1)
Into Result
From TABLE(TEST.pls_tab) t
Where mod(t.id,2) = 0;
Return Result;
END;
END:
To improve the performance of a table function, you can enable the function for parallel execution with the PARALLEL_ENABLE
option.
Functions enabled for parallel execution can run concurrently.
Pipeline the function results, with the PIPELINED option.
A pipelined table function returns a row to its invoker immediately after processing that row and continues to process rows. Response
time improves because the entire collection need not be constructed and returned to the server before the query can return a single
result row. (Also, the function needs less memory, because the object cache need not materialize the entire collection.)
Caution:
A pipelined table function always references the current state of the data. If the data in the collection changes after the cursor opens
for the collection, then the cursor reflects the changes. PL/SQL variables are private to a session and are not transactional. Therefore,
read consistency, well known for its applicability to table data, does not apply to PL/SQL collection variables.
25
First we test the regular table function by creating a new connection and querying a large collection. Checking the PGA memory
allocation before and after the test allows us to see how much memory was allocated as a result of the test.
-- Create a new session.
CONN test/test
-- Test table function.
SET SERVEROUTPUT ON
DECLARE
l_start NUMBER;
BEGIN
l_start := get_stat('session pga memory');
FOR cur_rec IN (SELECT *
FROM TABLE(get_tab_tf(100000)))
LOOP
NULL;
END LOOP;
DBMS_OUTPUT.put_line('Regular table function : ' ||
(get_stat('session pga memory') - l_start));
END;
/
Regular table function : 22872064
SQL>
Next, we repeat the test for the pipelined table function.
-- Create a new session.
CONN test/test
Cardinality
Oracle estimates the cardinality of a pipelined table function based on the database block size. When using the default block size, the
optimizer will always assume the cardinality is 8168 rows.
26
SET AUTOTRACE TRACE EXPLAIN
-- Return 10 rows.
SELECT *
FROM TABLE(get_tab_ptf(10));
Execution Plan
----------------------------------------------------------
Plan hash value: 822655197
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 16336 | 8 (0)| 00:02:19 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| GET_TAB_PTF | 8168 | 16336 | 8 (0)| 00:02:19 |
-------------------------------------------------------------------------------------------------
RETURN;
END;
/
Notice the p_cardinality parameter isn't used anywhere in the function itself you can use or not it is optional.
Next, we build a type and type body to set the cardinality manually. Notice the reference to the p_cardinality parameter in the type.
CREATE OR REPLACE TYPE t_ptf_stats AS OBJECT (
dummy INTEGER,
27
STATIC FUNCTION ODCIStatsTableFunction (
p_function IN SYS.ODCIFuncInfo,
p_stats OUT SYS.ODCITabFuncStats,
p_args IN SYS.ODCIArgDescList,
p_cardinality IN INTEGER
) RETURN NUMBER
);
/
SELECT *
FROM TABLE(get_tab_ptf(p_cardinality => 10));
Execution Plan
----------------------------------------------------------
Plan hash value: 822655197
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 20 | 8 (0)| 00:02:19 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| GET_TAB_PTF | 10 | 20 | 8 (0)| 00:02:19 |
-------------------------------------------------------------------------------------------------
28
SELECT *
FROM TABLE(get_tab_ptf(p_cardinality => 10000));
Execution Plan
----------------------------------------------------------
Plan hash value: 822655197
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10000 | 20000 | 8 (0)| 00:02:19 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| GET_TAB_PTF | 10000 | 20000 | 8 (0)| 00:02:19 |
-------------------------------------------------------------------------------------------------
RETURN;
END;
29
END;
/
SELECT *
FROM TABLE(ptf_api.get_tab_ptf(10))
ORDER BY id DESC;
ID DESCRIPTION
---------- --------------------------------------------------
10 Description for 10
9 Description for 9
8 Description for 8
7 Description for 7
6 Description for 6
5 Description for 5
4 Description for 4
3 Description for 3
2 Description for 2
1 Description for 1
10 rows selected.
SQL>
This seems like a better solution than having to build all the database types manually, but behind the scenes Oracle is building the
shadow object types implicitly and it sometimes make some bugs when calling the function or select from it.
COLUMN object_name FORMAT A30
OBJECT_NAME OBJECT_TYPE
------------------------------ -------------------
PTF_API PACKAGE BODY
SYS_PLSQL_82554_9_1 TYPE
SYS_PLSQL_82554_DUMMY_1 TYPE
SYS_PLSQL_82554_24_1 TYPE
PTF_API PACKAGE
5 rows selected.
SQL>
As you can see, Oracle has actually created three shadow object types with system generated names to support the types required by
the pipelined table function. For this reason I always build named database object types, rather than relying on the implicit types.
30
The basic syntax is shown below.
CREATE FUNCTION function-name(parameter-name ref-cursor-type)
RETURN rec_tab_type PIPELINED
PARALLEL_ENABLE(PARTITION parameter-name BY [{HASH | RANGE} (column-list) | ANY ]) IS
BEGIN
...
END;
To see it in action, first we must create and populate a test table.
CREATE TABLE parallel_test (
id NUMBER(10),
country_code VARCHAR2(5),
description VARCHAR2(50)
);
-- Check data.
SELECT country_code, count(*) FROM parallel_test GROUP BY country_code;
COUNT COUNT(*)
----- ----------
US 50000
IN 25000
UK 25000
3 rows selected.
SQL>
The following package defines parallel enabled pipelined table functions that accept ref cursors based on a query from the test table
and return the same rows, along with the SID of the session that processed them. We could use a weakly typed ref cursor, like
SYS_REFCURSOR, but this would restrict us to only the ANY partitioning type. The three functions represent the three partitioning
methods.
CREATE OR REPLACE PACKAGE parallel_ptf_api AS
31
TYPE t_parallel_test_tab IS TABLE OF t_parallel_test_row;
END parallel_ptf_api;
/
SELECT sid
INTO l_row.sid
FROM v$mystat
WHERE rownum = 1;
32
INTO l_row.id,
l_row.country_code,
l_row.description;
EXIT WHEN p_cursor%NOTFOUND;
SELECT sid
INTO l_row.sid
FROM v$mystat
WHERE rownum = 1;
SELECT sid
INTO l_row.sid
FROM v$mystat
WHERE rownum = 1;
END parallel_ptf_api;
/
The following query uses the CURSOR function to convert a query against the test table into a ref cursor that is past to the table
function as a parameter. The results are grouped by the SID of the session that processed the row. Notice all the rows were processed
by the same session. Why? Because although the function is parallel enabled, we didn't tell it to run in parallel.
SELECT sid, count(*)
FROM TABLE(parallel_ptf_api.test_ptf_any(CURSOR(SELECT * FROM parallel_test t1))) t2
GROUP BY sid;
SID COUNT(*)
---------- ----------
31 100000
1 row selected.
33
SQL>
The following queries include a parallel hint and call each of the functions.
SELECT country_code, sid, count(*)
FROM TABLE(parallel_ptf_api.test_ptf_any(CURSOR(SELECT /*+ parallel(t1, 5) */ * FROM parallel_test t1))) t2
GROUP BY country_code,sid
ORDER BY country_code,sid;
15 rows selected.
SQL>
3 rows selected.
SQL>
34
IN 40 25000
UK 23 25000
US 41 50000
3 rows selected.
SQL>
The degree of parallelism (DOP) may be lower than that requested in the hint.
An optional streaming clause can be used to order or cluster the data inside the server process based on a column list. This may be
necessary if data has dependencies, for example you wish to partition by a specific column, but also want the rows processed in a
specific order within that partition. The extended syntax is shown below.
CREATE FUNCTION function-name(parameter-name ref-cursor-type)
RETURN rec_tab_type PIPELINED
PARALLEL_ENABLE(PARTITION parameter-name BY [{HASH | RANGE} (column-list) | ANY ])
[ORDER | CLUSTER] parameter-name BY (column-list) IS
BEGIN
...
END;
You may wish to do something like the following for example.
FUNCTION test_ptf_hash (p_cursor IN t_parallel_test_ref_cursor)
RETURN t_parallel_test_tab PIPELINED
PARALLEL_ENABLE(PARTITION p_cursor BY HASH (country_code))
ORDER p_cursor BY (country_code, created_date);
Transformation Pipelines
In traditional Extract Transform Load (ETL) processes you may be required to load data into a staging area, then make several passes
over it to transform it into a state where it can be loaded into your destination schema. Passing the data through staging tables can
represent a significant amount of disk I/O for both the data and the redo generated. An alternative is to perform the transformations in
pipelined table functions so data can be read from an external table and inserted directly into the destination table, removing much of
the disk I/O.
In this section we will see and example of this using the techniques discussed previously to build a transformation pipeline.
First, generate some test data as a flat file by spooling it out to the database server's file system.
SET PAGESIZE 0
SET FEEDBACK OFF
SET LINESIZE 1000
SET TRIMSPOOL ON
SPOOL /tmp/tp_test.txt
SELECT owner || ',' || object_name || ',' || object_type || ',' || status
FROM all_objects;
SPOOL OFF
SET FEEDBACK ON
SET PAGESIZE 24
Create a directory object pointing to the location of the file, create an external table to read the file and create a destination table.
-- Create a directory object pointing to the flat file.
CONN / AS SYSDBA
35
CREATE OR REPLACE DIRECTORY data_load_dir AS '/tmp/';
GRANT READ, WRITE ON DIRECTORY data_load_dir TO test;
CONN test/test
-- Create an external table.
DROP TABLE tp_test_ext;
CREATE TABLE tp_test_ext (
owner VARCHAR2(30),
object_name VARCHAR2(30),
object_type VARCHAR2(19),
status VARCHAR2(7)
)
ORGANIZATION EXTERNAL
(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY data_load_dir
ACCESS PARAMETERS
(
RECORDS DELIMITED BY NEWLINE
BADFILE data_load_dir:'tp_test_%a_%p.bad'
LOGFILE data_load_dir:'tp_test_%a_%p.log'
FIELDS TERMINATED BY ','
MISSING FIELD VALUES ARE NULL
(
owner CHAR(30),
object_name CHAR(30),
object_type CHAR(19),
status CHAR(7)
)
)
LOCATION ('tp_test.txt')
)
PARALLEL 10
REJECT LIMIT UNLIMITED
/
36
TYPE t_step_1_out_row IS RECORD (
owner VARCHAR2(30),
object_name VARCHAR2(30),
object_type VARCHAR2(19),
status VARCHAR2(7),
extra_1 NUMBER
);
PROCEDURE load_data;
END tp_api;
/
37
FUNCTION step_2 (p_cursor IN t_step_2_in_rc)
RETURN t_step_2_out_tab PIPELINED
PARALLEL_ENABLE(PARTITION p_cursor BY ANY)
IS
l_row tp_test%ROWTYPE;
BEGIN
LOOP
FETCH p_cursor
INTO l_row.owner,
l_row.object_name,
l_row.object_type,
l_row.status,
l_row.extra_1;
EXIT WHEN p_cursor%NOTFOUND;
PROCEDURE load_data IS
BEGIN
EXECUTE IMMEDIATE 'ALTER SESSION ENABLE PARALLEL DML';
EXECUTE IMMEDIATE 'TRUNCATE TABLE tp_test';
END tp_api;
/
The insert inside the LOAD_DATA procedure represents the whole data load including the transformations. The statement looks
quite complicated, but it is made up of the following simple steps.
The rows are queried from the external table.
These are converted into the ref cursor using the CURSOR function.
This ref cursor is passed to the first stage of the transformation (STEP_1).
The return collection from STEP_1 is queried using the TABLE function.
The output of this query is converted to a ref cursor using the CURSOR function.
38
This ref cursor is passed to the second stage of the transformation (STEP_2).
The return collection from STEP_2 is queried using the TABLE function.
This query is used to drive the insert into the destination table.
By calling the LOAD_DATA procedure we can transform and load the data.
EXEC tp_api.load_data;
COUNT(*)
----------
56059
1 row selected.
SQL>
-- Compare to the destination table.
SELECT COUNT(*) FROM tp_test;
COUNT(*)
----------
56059
1 row selected.
SQL>
AUTONOMOUS_TRANSACTION Pragma
If the pipelined table function runs DML statements, then make it autonomous, with the AUTONOMOUS_TRANSACTION pragma
then, during parallel execution, each instance of the function creates an independent transaction.
SET TIMING ON
SET ARRAYSIZE 15
SELECT slow_function(id)
39
FROM func_test;
SLOW_FUNCTION(ID)
-----------------
1
2
1
2
1
2
1
2
1
3
10 rows selected.
Elapsed: 00:00:04.04
SQL>
SET TIMING ON
SET ARRAYSIZE 2
SELECT slow_function(id)
FROM func_test;
SLOW_FUNCTION(ID)
-----------------
1
2
1
2
1
2
1
2
1
3
10 rows selected.
Elapsed: 00:00:10.01
SQL>
The difference in array size produced drastically different performance, showing that caching is only available for the lifetime of the
fetch. Subsequent queries (or fetches) have no access to the cached values of previous runs.
RETURN Data Type
The data type of the value that a pipelined table function returns must be a collection type defined either at schema level or inside a
package (therefore, it cannot be an associative array type). The elements of the collection type must be SQL data types, not data types
supported only by PL/SQL (such as PLS_INTEGER and BOOLEAN)
40
PIPE ROW Statement
Inside a pipelined table function, use the PIPE ROW statement to return a collection element to the invoker without returning control
to the invoke
RETURN Statement
As in every function, every execution path in a pipelined table function must lead to a RETURN statement, which returns control to
the invoker. However, in a pipelined table function, a RETURN statement need not return a value to the invoker
SET TIMING ON
SELECT slow_function(id)
FROM func_test;
SLOW_FUNCTION(ID)
-----------------
1
2
1
2
1
2
1
2
1
3
10 rows selected.
Elapsed: 00:00:03.09
SQL>
The advantage of this method is the cached information can be reused by any session and dependencies are managed automatically. If
we run the query again we get even better performance because we can used the cached values without calling the function at all.
SET TIMING ON
SELECT slow_function(id)
41
FROM func_test;
SLOW_FUNCTION(ID)
-----------------
1
2
1
2
1
2
1
2
1
3
10 rows selected.
Elapsed: 00:00:00.02
SQL>
SQL Hints
There are many Oracle hints available to the developer for use in tuning SQL statements that are embedded in PL/SQL.
You should first get the explain plan of your SQL and determine what changes can be done to make the code operate without using
hints if possible. However, Oracle hints such as ORDERED, LEADING, INDEX, FULL, and the various AJ and SJ Oracle hints can
take a wild optimizer and give you optimal performance.
Oracle hints are enclosed within comments to the SQL commands DELETE, SELECT or UPDATE or are designated by two dashes
and a plus sign. To show the format the SELECT statement only will be used, but the format is identical for all three commands.
SELECT /*+ hint --or-- text */ statement body
-- or --
SELECT --+ hint --or-- text statement body
Where:
42
FULL(table) This tells the optimizer to do a full scan of the specified table.
HASH(table) Tells Oracle to explicitly choose the hash access method for the table.
HASH_AJ(table) Transforms a NOT IN subquery to a hash anti-join.
ROWID(table) Forces a rowid scan of the specified table.
Forces an index scan of the specified table using the specified index(s). If a list of indexes is
INDEX(table [index]) specified, the optimizer chooses the one with the lowest cost. If no index is specified then the
optimizer chooses the available index for the table with the lowest cost.
Same as INDEX only performs an ascending search of the index chosen, this is functionally
INDEX_ASC (table [index])
identical to the INDEX statement.
Same as INDEX except performs a descending search. If more than one table is accessed, this is
INDEX_DESC(table [index])
ignored.
Combines the bitmapped indexes on the table if the cost shows that to do so would give better
INDEX_COMBINE(table index)
performance.
INDEX_FFS(table index) Perform a fast full index scan rather than a table scan.
MERGE_AJ (table) Transforms a NOT IN subquery into a merge anti-join.
AND_EQUAL(table index index
This hint causes a merge on several single column indexes. Two must be specified, five can be.
[index index index])
NL_AJ Transforms a NOT IN subquery into a NL anti-join (nested loop).
Inserted into the EXISTS subquery; This converts the subquery into a special type of hash join
HASH_SJ(t1, t2) between t1 and t2 that preserves the semantics of the subquery. That is, even if there is more
than one matching row in t2 for a row in t1, the row in t1 is returned only once.
Inserted into the EXISTS subquery; This converts the subquery into a special type of merge join
MERGE_SJ (t1, t2) between t1 and t2 that preserves the semantics of the subquery. That is, even if there is more
than one matching row in t2 for a row in t1, the row in t1 is returned only once.
Inserted into the EXISTS subquery; This converts the subquery into a special type of nested
NL_SJ loop join between t1 and t2 that preserves the semantics of the subquery. That is, even if there is
more than one matching row in t2 for a row in t1, the row in t1 is returned only once.
Oracle Hints for join orders and
transformations:
This hint forces tables to be joined in the order specified. If you know table X has fewer rows,
ORDERED
then ordering it first may speed execution in a join.
STAR Forces the largest table to be joined last using a nested loops join on the index.
STAR_TRANSFORMATION Makes the optimizer use the best plan in which a start transformation is used.
FACT(table) When performing a star transformation use the specified table as a fact table.
NO_FACT(table) When performing a star transformation do not use the specified table as a fact table.
This causes nonmerged subqueries to be evaluated at the earliest possible point in the execution
PUSH_SUBQ
plan.
If possible forces the query to use the specified materialized view, if no materialized view is
REWRITE(mview)
specified, the system chooses what it calculates is the appropriate view.
Turns off query rewrite for the statement, use it for when data returned must be concurrent and
NOREWRITE
can't come from a materialized view.
Forces combined OR conditions and IN processing in the WHERE clause to be transformed
USE_CONCAT
into a compound query using the UNION ALL set operator.
This causes Oracle to join each specified table with another row source without a sort-merge
NO_MERGE (table)
join.
NO_EXPAND Prevents OR and IN processing expansion.
43
Oracle Hints for Join Operations:
USE_HASH (table)
This causes Oracle to join each specified table with another row source with a hash join.
USE_NL(table) This operation forces a nested loop using the specified table as the controlling table.
USE_MERGE(table,[table, - ]) This operation forces a sort-merge-join operation of the specified tables.
The hint forces query execution to be done at a different site than that selected by Oracle. This
DRIVING_SITE
hint can be used with either rule-based or cost-based optimization.
LEADING(table) The hint causes Oracle to use the specified table as the first table in the join order.
Oracle Hints for Parallel
Operations:
This specifies that data is to be or not to be appended to the end of a file rather than into existing
[NO]APPEND
free space. Use only with INSERT commands.
NOPARALLEL (table This specifies the operation is not to be done in parallel.
PARALLEL(table, instances) This specifies the operation is to be done in parallel.
PARALLEL_INDEX Allows parallelization of a fast full index scan on any index.
Other Oracle Hints:
Specifies that the blocks retrieved for the table in the hint are placed at the most recently used
CACHE
end of the LRU list when the table is full table scanned.
Specifies that the blocks retrieved for the table in the hint are placed at the least recently used
NOCACHE
end of the LRU list when the table is full table scanned.
[NO]APPEND For insert operations will append (or not append) data at the HWM of table.
Turns on the UNNEST_SUBQUERY option for statement if UNNEST_SUBQUERY
UNNEST
parameter is set to FALSE.
Turns off the UNNEST_SUBQUERY option for statement if UNNEST_SUBQUERY
NO_UNNEST
parameter is set to TRUE.
PUSH_PRED Pushes the join predicate into the view.
As you can see, a dilemma with a stubborn index can be easily solved using FULL or NO_INDEX Oracle hints. You must know the
application to be tuned. The DBA can provide guidance to developers but in all but the smallest development projects, it will be
nearly impossible for a DBA to know everything about each application. It is clear that responsibility for application tuning rests
solely on the developer's shoulders with help and guidance from the DBA.
44
Some Important Database parameters and hidden
parameters
DB file Multiblock read count
Oracle notes that the cost of reading the blocks from disk into the buffer cache can be amortized by reading the blocks in large I/O
operations. The db_file_multiblock_read_count parameter controls the number of blocks that are pre-fetched into the buffer cache if a
cache miss is encountered for a particular block.
The value of this parameter can have a significant impact on the overall database performance and it is not easy for the administrator
to determine its most appropriate value. Oracle Database 10g Release 2 automatically selects the appropriate value for this parameter
depending on the operating system optimal I/O size and the size of the buffer cache
The Oracle database improves the performance of table scans by increasing the number of blocks read in a single database I/O
operation. If your SQL statement is going to read all of the rows in a table, it makes sense to return as many blocks as you can in a
single read. In releases prior to Oracle10G R2, administrators used the db_file_multiblock_read_count initialization parameter to tell
Oracle how many block to retrieve in the single I/O operation.
But setting the db_file_multiblock_read_count parameter too high can affect access path selection. Full table scans use multi-block
reads, so the cost of a full table scan depends on the number of multi-block reads required to read the entire table. The more blocks
retrieved in a single multi-block I/O execution, the more favorable a tablescan looks to the optimizer.
In releases prior to Oracle10G R2, the permitted values for db_file_multiblock_read_count were platform-dependent. The most
common settings ranged from 4 to 64 blocks per single multi-block I/O execution.
The DB_FILE_MULTIBLOCK_READ_COUNT parameter controls the number of blocks pre-fetched into the buffer cache during
scan operations, such as full table scan and index fast full scan.
Oracle Database 10g Release 2 automatically selects the appropriate value for this parameter depending on the operating system
optimal I/O size and the size of the buffer cache.
This is the default behavior in Oracle Database 10g Release 2, if you do not set any value for the db_file_multiblock_read_count
parameter (i.e. removing it from your spfile or init.ora file). If you explicitly set a value, then that value is used, and is consistent with
the previous behavior.
Remember, the parameter db_file_multiblock_read_count is only applicable for tables/indexes that are full scanned, but it also effects
the SQL optimizer in its calculation of the cost of a full-table scan.
45
-------------------------------------------------------------------------------
--
-- Script: multiblock_read_test.sql
-- Purpose: find largest actual multiblock read size
--
--
-- Description: This script prompts the user to enter the name of a table to
-- scan, and then does so with a large multiblock read count, and
-- with event 10046 enabled at level 8.
-- The trace file is then examined to find the largest multiblock
-- read actually performed.
--
-------------------------------------------------------------------------------
@save_sqlplus_settings
prompt
@accept Table "Table to scan" SYS.SOURCE$
prompt Scanning ...
set termout off
alter session set events '10046 trace name context forever, level 8'
/
select /*+ full(t) noparallel(t) nocache(t) */ count(*) from &Table t
/
alter session set events '10046 trace name context off'
/
set termout on
@trace_file_name
prompt
prompt Maximum effective multiblock read count
prompt ----------------------------------------
46
Oracle Direct I/O
Many Oracle shops are plagued with slow I/O intensive databases, and this tip is for anyone whose STATSPACK top-5 timed events
shows disk I/O as a major event:
Top 5 Timed Events
% Total
Event Waits Time (s) Ela Time
--------------------------- ------------ ----------- -----------
db file sequential read 2,598 7,146 48.54
db file scattered read 25,519 3,246 22.04
library cache load lock 673 1,363 9.26
CPU time 2,154 934 7.83
log file parallel write 19,157 837 5.68
Oracle direct I/O should be verified for Solaris, HP/UX, and Linux AIX. This tip is important to you if you have reads waits in your
top-5 timed events. Remember, if disk I/O is not your bottleneck then making it faster WILL NOT improve performance.
Also, this is an OS-level solution, and often I/O-bound Oracle databases can be fixed by tuning the SQL to reduce unnecessary large-
table full-table scans. I monitor file I/O using the stats$filestatxs view
Oracle controls direct I/O with a parameter named filesystemio_options. According to the Oracle documentation the
filesystemio_options parameter must be set to "setall" (the preferred method, according to the Oracle documentation) or ”directio" in
order for Oracle to read data blocks directly from disk
Using direct I/O allows you to enhance I/O by bypassing the redundant OS block buffers, reading the data block directly into the
Oracle SGA. Using direct I/O also allow you to create multiple blocksized tablespaces to improve I/O performance
The pga_aggregate_target parameters to fix this resource issue, and by-and-large, pga_aggregate_target works very well for most
systems. You can check your overall PGA usage with the v$pga_target_advice advisory utility or a STATSPACK or AWR
report. High values for multi-pass executions, high disk sorts, or low hash join invocation might indicate a low resource usage for
PGA regions.
For monitoring pga_aggregate_target Oracle provides a dictionary view called v$pgastat. The v$pgastat view shows the total amount
of RAM memory utilization for every RAM memory region within the database.
You can also increase your pga_aggregate_target above the default 200 megabyte setting by setting the hidden _pga_max_size
parameter.
_pga_max_size = 1000m
_smm_px_max_size = 333m
With pga_aggregate_target and _pga_max_size hidden parameter set to 1G we see a 5x improvement over the default for parallel
queries and sorts:
A RAM sort or hash join may now have up to 50 megabytes (5% of pga_aggegate_target).
47
Parallel queries may now have up to 330 megabytes of RAM (30% of pga_aggegate_target), such that a DEGREE=4
parallel query would have 83 megabytes (333 meg/4).
When an Oracle process requires an operation, such as a sort or a hash join, it goes to the shared RAM memory area within
pga_aggregate_target region and attempts to obtain enough contiguous RAM frames to perform the operation. If the process is able
to acquire these RAM frames immediately from pga_aggregate_target, it is marked as an "optimal" RAM access.
If the RAM acquisition requires a single pass through pga_aggregate_target, the RAM memory allocation is marked as one pass. If all
RAM is in use, Oracle may have to make multiple passes through pga_aggregate_target to acquire the RAM memory. This is called
multipass.
Remember, RAM memory is extremely fast, and most sorts or hash joins are completed in microseconds. Oracle allows a single
process to use up to 5 percent of the pga_aggregate_target, and parallel operations are allowed to consume up to 30 percent of the
PGA RAM pool.
Pga_aggregate_target "multipass" executions indicate a RAM shortage, and you should always allocate enough RAM to ensure that at
least 95 percent of connected tasks can acquire their RAM memory optimally.
You can also obtain information about workarea executions by querying the v$sysstat view shown here:
Note:
When oracle couldn’t find enough ram to sort then it will use TEMP tablespace that may cause lots of I/O and it will impact the
performance
48
All else being equal, insert-intensive databases will perform less write I/O (via the DBWR process) with larger block sizes because
more "logical inserts" can take place within the data buffer before the block becomes full and requires writing it back to disk.
At first, beginners denounced multiple block sizes because they were invented to support transportable tablespaces. Fortunately,
Oracle has codified the benefits of multiple blocksizes, and the Oracle Performance Tuning Guide notes that multiple blocksizes are
indeed beneficial in large databases to eliminate superfluous I/O and isolate critical objects into a separate data buffer cache:
? With segments that have atypical access patterns, store blocks from those segments in two different buffer pools: the KEEP pool and
the RECYCLE pool.
A segment's access pattern may be atypical if it is constantly accessed (that is, hot) or infrequently accessed (for example, a large
segment accessed by a batch job only once a day).
Multiple buffer pools let you address these differences. You can use a KEEP buffer pool to maintain frequently accessed segments in
the buffer cache, and a RECYCLE buffer pool to prevent objects from consuming unnecessary space in the cache. . .
By allocating objects to appropriate buffer pools, you can:
Reduce or eliminate I/Os
Isolate or limit an object to a separate cache
For example:
When the 16k instance runs an 850,000 row update (nowhere clause), it finishes in 45 minutes. When the 4k instance runs an 850,000
row update (nowhere clause), it finishes in 2.2 minutes. The change in blocksize caused the job to run TWENTY TIMES FASTER
COUNT(MYFIELD)
-------------------
164864
Elapsed: 00:00:01.40
...
(This command is executed several times - the execution time was approximately the same ~
00:00:01.40)
And now the test with the same table, but created together with the index in 16k tablespace:
SQL> r
1 select count(MYFIELD) from table_16K where ttime >to_date('27/09/2006','dd/mm/
2* and ttime <to_date('06/10/2006','dd/mm/yyyy')
COUNT(MYFIELD)
-------------------
164864
Elapsed: 00:00:00.36
(Again, the command is executed several times, the new execution time is approximately the same ~
49
By performing block reads of an appropriate size the DBA can significantly increase the efficiency of the data buffers. For example,
consider an OLTP database that randomly reads 80 byte customer rows. If you have a 16k db_block_size, Oracle must read all of the
16k into the data buffer to get your 80 bytes, a waste of data buffer resources. If we migrate this customer table into a 2k blocksize,
we now only need to read-in 2k to get the row data. This results in 8 times more available space for random block fetches.
50
Improvements in data buffer utilization
Robin Schumacher has proven in his book Oracle Performance Troubleshooting (2003, Rampant TechPress) that Oracle b-tree
indexes are built in flatter structures in 32k blocksizes. We also see a huge reduction in logical I/O during index range scans and
sorting within the TEMP tablespace because adjacent rows are located inside the same data block.
51
We can also identify those indexes with the most index range scans with this simple AWR script.
col c1 heading ?Object|Name? format a30
col c2 heading ?Option? format a15
col c3 heading ?Index|Usage|Count? format 999,999
select
p.object_name c1,
p.options c2,
count(1) c3
from
dba_hist_sql_plan p,
dba_hist_sqlstat s
where
p.object_owner <> 'SYS'
and
p.options like '%RANGE SCAN%'
and
p.operation like ?%INDEX%?
and
p.sql_id = s.sql_id
group by
p.object_name,
p.operation,
p.options
order by
1,2,3;
Here is the output where we see overall total counts for each object and table access method.
52
Object Usage
Name Option Count
----------------------------- --------------- --------
CUSTOMER_CHECK RANGE SCAN 4,232
AVAILABILITY_PRIMARY_KEY RANGE SCAN 1,783
CON_UK RANGE SCAN 473
CURRENT_SEVERITY RANGE SCAN 323
CWM$CUBEDIMENSIONUSE_IDX RANGE SCAN 72
ORDERS_FK RANGE SCAN 20
By segregating high activity tables into a separate, smaller data buffer, Oracle has far less RAM frames to scan for dirty block,
improving the throughout and also reducing CPU consumption. This is especially important for super-high update tables with more
than 100 row changes per second.
In general, the Oracle cost-based optimizer is unaware of buffers details (except when you set the optimizer_index_caching
parameter), and using multiple data buffers will not impact SQL execution plans. When data using the new cpu_cost parameter in
Oracle10g, the Oracle SQL optimizer builds the SQL plan decision tree based on the execution plan that will have the lowest
estimated CPU cost.
For example, if you implement a 32k data buffer for your index tablespaces you can ensure that your indexes are caches for optimal
performance and minimal logical I/O in range scans.
For example, if my database has 50 gigabytes of index space, I can define a 60 gigabyte db_32k_cache_size and then set my
optimizer_index_caching parameter to 100, telling the SQL optimizer that all of my Oracle indexes reside in RAM. Remember when
53
Oracle makes the index vs. table scan decision, knowing that the index nodes are in RAM will greatly influence the optimizer because
the CBO knows that a logical I/O is often 100 times faster than a physical disk read.
In sum, moving Oracle indexes into a fully-cached 32k buffer will ensure that Oracle favors index access, reducing unnecessary full-
table scans and greatly reducing logical I/O because adjacent index nodes will reside within the larger, 32k block.
Largest Benefit:
We see the largest benefit of multiple blocksizes in these types of databases:
OLTP databases: Databases with a large amount of index access (first_rows optimizer_mode) and databases with random fetches of
small rows are ideal for buffer pool segregation.
64-bit Oracle databases: Oracle databases with 64-bit software can support very large data buffer caches and these are ideal for
caching frequently-referenced tables and indexes.
High-update databases: Databases where a small sub-set of the database receives large update activity (i.e. a single partition within a
table), will see a large reduction in CPU consumption when the high update objects are moved into a smaller buffer cache.
Smallest benefit:
However, there are specific types of databases that may not benefit from using multiple blocksizes:
Small node Oracle10g Grid systems: Because each data blade in an Oracle10g grid node has only 2 to 4 gigabytes of RAM, data
blade grid applications do not show a noticeable benefit from multiple block sizes.
Solid-state databases: Oracle databases using solid-state disks (RAM-SAN) perform fastest with super-small data buffer, just large
enough to hold the Oracle serialization locks and latches.
Decision Support Systems: Large Oracle data warehouses with parallel large-table full table scans do not benefit from multiple
blocksizes. Remember, parallel full-table scans bypass the data buffers and store the intermediate rows sets in the PGA region. As a
general rule, databases with the all_rows optimizer_mode may not benefit from multiple blocksizes.
Even though Oracle introduced multiple blocksizes for a innocuous reason, their power has become obvious in very large database
systems. The same divide-and-conquer approach that Oracle has used to support very large databases can also be used to divide and
conquer your Oracle data buffers.
Important Tips:
Objects that experience full-table scans should be placed in a larger block size, with db_file_multiblock_read_count set to the block
size of that tablespace
54
By defining large (i.e. 32k) blocksizes for the target table, you reduce I/O because more rows fit onto a block before a "block full"
condition (as set by PCTFREE) unlinks the block from the freelist.
Small rows in a large block perform worse under heavy DML than large rows in a small blocksize.
Heavy insert/update tables can see faster performance when segregated into another blocksize which is mapped to a small data buffer
cache. Smaller data buffer caches often see faster throughput performance.
RAC can perform far faster with smaller blocksizes, reducing cache fusion overhead
Moving random access small row tables to a smaller blocksize (with a corresponding small blocksize buffer) will reduce buffer waste
and improve the chance that other data blocks will remain in the cache.
Tables and indexes that require full scans can see faster performance when placed in a large blocksize
If you are not setting db_cache_size to the largest supported block size for your server, you should not use the db_recycle_cache_size
parameter. Instead, you will want to create a db_32k_cache_size (or whatever your max is), and assign all tables that experience
frequent large-table full-table scans to the largest buffer cache in your database.
Oracle recommend 2K blocksizes for bitmap indexes, to minimize redo generation during bitmap index rebuilds
For those databases that fetch small rows randomly from the disk, the Oracle DBA can segregate these types of tables into 2K
Tablespaces. We have to remember that while disk is becoming cheaper every day, we still don't want to waste any available RAM by
reading in more information to RAM than the number actually going be used by the query. Hence, many Oracle DBAs will use small
block size is in cases of tiny, random access record retrieval.
For those Oracle tables that contain raw, long raw, or in-line LOBs, moving the table rows to large block size will have an extremely
beneficial effect on disk I/O. Experienced DBAs will check dba_tables.avg_row_len to make sure that the blocksize is larger than the
average size. Row chaining will be reduced while at the same time the entire LOB can be read within a single disk I/O, thereby
avoiding the additional overhead of having Oracle to go out of read multiple blocks.
The block size for a tables' tablespace should always be greater than the average row length for the table (dba_tables.avg_row_len).
Not it is smaller than the average row length, rows chaining occurs and excessive disk I/O is incurred.
The following test is an example of the performance gains available for pure procedural code.
ALTER SESSION SET plsql_compiler_flags = 'INTERPRETED';
55
END;
/
SET TIMING ON
EXEC test_speed;
Elapsed: 00:00:01.00
EXEC test_speed;
Elapsed: 00:00:00.05
From this you can see that a basic procedural operation can be speeded up significantly. In this example the natively compiled code
takes approximately 1/20 of the time to run.
The speed of database calls is unaffected by native compilation so the performance increases of most stored procedures will not be so
dramatic. This is illustrated by the following example.
ALTER SESSION SET plsql_compiler_flags = 'INTERPRETED';
EXEC test_speed;
Elapsed: 00:02:21.04
EXEC test_speed;
Elapsed: 00:02:19.04
56
Stop Making the Same Performance Mistakes
PL/SQL is great, but like any programming language it is capable of being misused. This article highlights the common performance
mistakes made when developing in PL/SQL, turning what should be an elegant solution into a resource hog. This is very much an
overview, but each section includes links to the relevant articles on this site that discuss the topic in greater depth, including example
code, so think of this more like a check-list of things to avoid, that will help you get the best performance from your PL/SQL.
57
The BULK COLLECT clause allows you to pull multiple rows back into a collection. The FORALL construct allows you to bind all
the data in a collection into a DML statement. In both cases, the performance improvements are achieved by reducing the number of
context switches between PL/SQL and SQL that are associated with row-by-row processing.
Quick Points
Stop doing index scans when you can use ROWIDs.
Stop using explicit cursors.
Stop putting your code in the wrong order. Take advantage of performance gains associated with short-circuit evaluation and
logic/branch ordering in PL/SQL.
Stop doing intensive processing immediately if it is more appropriate to decouple it.
Stop calling PL/SQL functions in your SQL statements. If you must do it, make sure you use the most efficient manner
possible.
Stop avoiding code instrumentation (DBMS_APPLICATION_INFO and DBMS_SESSION). It's a very quick way to
identify problems.
Stop avoiding PL/SQL native compilation.
Stop avoiding conditional compilation where it is appropriate. The easiest way to improve the speed of doing something is to
avoid doing it in the first place.
Stop reinventing the wheel. Oracle has many built-in packages, procedures and functions that will probably do the job much
more efficiently than you will, so learn them and use them
58
Real Number Data Types
SET SERVEROUTPUT ON
DECLARE
l_number1 NUMBER := 1;
l_number2 NUMBER := 1;
l_integer1 INTEGER := 1;
l_integer2 INTEGER := 1;
l_pls_integer1 PLS_INTEGER := 1;
l_pls_integer2 PLS_INTEGER := 1;
l_binary_integer1 BINARY_INTEGER := 1;
l_binary_integer2 BINARY_INTEGER := 1;
l_simple_integer1 BINARY_INTEGER := 1;
l_simple_integer2 BINARY_INTEGER := 1;
l_loops NUMBER := 10000000;
l_start NUMBER;
BEGIN
-- Time NUMBER.
l_start := DBMS_UTILITY.get_time;
DBMS_OUTPUT.put_line('NUMBER : ' ||
(DBMS_UTILITY.get_time - l_start) || ' hsecs');
-- Time INTEGER.
l_start := DBMS_UTILITY.get_time;
DBMS_OUTPUT.put_line('INTEGER : ' ||
(DBMS_UTILITY.get_time - l_start) || ' hsecs');
-- Time PLS_INTEGER.
l_start := DBMS_UTILITY.get_time;
DBMS_OUTPUT.put_line('PLS_INTEGER : ' ||
(DBMS_UTILITY.get_time - l_start) || ' hsecs');
59
-- Time BINARY_INTEGER.
l_start := DBMS_UTILITY.get_time;
DBMS_OUTPUT.put_line('BINARY_INTEGER : ' ||
(DBMS_UTILITY.get_time - l_start) || ' hsecs');
-- Time SIMPLE_INTEGER.
l_start := DBMS_UTILITY.get_time;
DBMS_OUTPUT.put_line('SIMPLE_INTEGER : ' ||
(DBMS_UTILITY.get_time - l_start) || ' hsecs');
END;
/
NUMBER : 52 hsecs
INTEGER : 101 hsecs
PLS_INTEGER : 17 hsecs
BINARY_INTEGER : 17 hsecs
SIMPLE_INTEGER : 19 hsecs
SQL>
SET SERVEROUTPUT ON
DECLARE
l_number1 NUMBER := 1.1;
l_number2 NUMBER := 1.1;
l_binary_float1 BINARY_FLOAT := 1.1;
l_binary_float2 BINARY_FLOAT := 1.1;
l_simple_float1 SIMPLE_FLOAT := 1.1;
l_simple_float2 SIMPLE_FLOAT := 1.1;
l_binary_double1 BINARY_DOUBLE := 1.1;
60
l_binary_double2 BINARY_DOUBLE := 1.1;
l_simple_double1 SIMPLE_DOUBLE := 1.1;
l_simple_double2 SIMPLE_DOUBLE := 1.1;
l_loops NUMBER := 10000000;
l_start NUMBER;
BEGIN
-- Time NUMBER.
l_start := DBMS_UTILITY.get_time;
DBMS_OUTPUT.put_line('NUMBER : ' ||
(DBMS_UTILITY.get_time - l_start) || ' hsecs');
-- Time BINARY_FLOAT.
l_start := DBMS_UTILITY.get_time;
DBMS_OUTPUT.put_line('BINARY_FLOAT : ' ||
(DBMS_UTILITY.get_time - l_start) || ' hsecs');
-- Time SIMPLE_FLOAT.
l_start := DBMS_UTILITY.get_time;
DBMS_OUTPUT.put_line('SIMPLE_FLOAT : ' ||
(DBMS_UTILITY.get_time - l_start) || ' hsecs');
-- Time BINARY_DOUBLE.
l_start := DBMS_UTILITY.get_time;
DBMS_OUTPUT.put_line('BINARY_DOUBLE : ' ||
(DBMS_UTILITY.get_time - l_start) || ' hsecs');
-- Time SIMPLE_DOUBLE.
l_start := DBMS_UTILITY.get_time;
DBMS_OUTPUT.put_line('SIMPLE_DOUBLE : ' ||
(DBMS_UTILITY.get_time - l_start) || ' hsecs');
END;
/
NUMBER : 56 hsecs
BINARY_FLOAT : 25 hsecs
SIMPLE_FLOAT : 26 hsecs
61
BINARY_DOUBLE : 33 hsecs
SIMPLE_DOUBLE : 34 hsecs
SQL>
Both BINARY_FLOAT and BINARY_DOUBLE out-perform the NUMBER data type, but the fact they use machine
arithmetic can potentially make them less portable. The same mathematical operations performed on two different underlying
architectures may result in minor rounding errors. If portability is your primary concern, then you should use the NUMBER type,
otherwise you can take advantage of these types for increased performance.
Oracle 12.2 introduced the concept of real-time materialized views, which allow a statement-level wind-
forward of a stale materialised view, making the data appear fresh to the statement. This wind-forward is based
on changes computed using materialized view logs, similar to a conventional fast refresh, but the operation only
affect the current statement. The changes are not persisted in the materialized view, so a conventional refresh is
still required at some point.
The real-time materialized functionality has some restrictions associated with it including the following.
62
The materialized view must be capable of a fast refresh, so all the typical fast refresh restrictions apply
here also.
The materialized view can't use database links. I don't think this is a problem as I see this as a solution
for real-time reporting and dashboards, rather than part of a distributed environment.
The materialized view must use the ENABLE ON QUERY COMPUTATION option.
Queries making direct references to a materialized view will not use the real-time materialized view
functionality by default. To use this functionality the query much use the FRESH_MV hint.
The rest of this article provides some simple examples of real-time materialized views.
Setup
We need a table to act as the source of the materialized view. The following script creates and populates a test
table with random data.
CONN test/test@pdb1
63
Materialized View
We can now create the materialized view. Notice the ENABLE ON QUERY COMPUTATION option, which is new to
Oracle 12.2.
Basic Rewrite
As with previous versions of the database, if we run a query that could be serviced quicker by the materialized
view, Oracle will rewrite the query to use the materialized view.
SELECT order_id,
SUM(line_qty) AS sum_line_qty,
SUM(total_value) AS sum_total_value,
COUNT(*) AS row_count
FROM order_lines
WHERE order_id = 1
GROUP BY order_id;
SQL>
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------
----------
SQL_ID 3rttkdd0ybtaw, child number 0
-------------------------------------
SELECT order_id, SUM(line_qty) AS sum_line_qty,
SUM(total_value) AS sum_total_value, COUNT(*) AS row_count FROM
order_lines WHERE order_id = 1 GROUP BY order_id
64
-----------------------------------------------------------------------------------------
----------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
Time |
-----------------------------------------------------------------------------------------
----------
| 0 | SELECT STATEMENT | | | | 4 (100)| |
|* 1 | MAT_VIEW REWRITE ACCESS FULL| ORDER_SUMMARY_RTMV | 1 | 17 | 4 (0)|
00:00:01 |
-----------------------------------------------------------------------------------------
----------
1 - filter("ORDER_SUMMARY_RTMV"."ORDER_ID"=1)
20 rows selected.
SQL>
We can see from the execution plan the materialized view was used, rather than accessing the base table. Notice
the row count value of 95.
SELECT mview_name,
staleness,
on_query_computation
FROM user_mviews;
MVIEW_NAME STALENESS O
------------------------------ ------------------- -
ORDER_SUMMARY_RTMV NEEDS_COMPILE Y
SQL>
A regular materialized view would no longer be considered for query rewrites unless we had the
QUERY_REWRITE_INTEGRITY parameter set to STALE_TOLERATED for the session. Since we have the ENABLE ON
QUERY COMPUTATION option on the materialized view it is still considered usable, as Oracle will dynamically
amend the values to reflect the changes in the materialized view logs.
SELECT order_id,
SUM(line_qty) AS sum_line_qty,
SUM(total_value) AS sum_total_value,
COUNT(*) AS row_count
FROM order_lines
WHERE order_id = 1
GROUP BY order_id;
65
ORDER_ID SUM_LINE_QTY SUM_TOTAL_VALUE ROW_COUNT
---------- ------------ --------------- ----------
1 910 54573.88 96
SQL>
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------
-------------------------
SQL_ID 3rttkdd0ybtaw, child number 1
-------------------------------------
SELECT order_id, SUM(line_qty) AS sum_line_qty,
SUM(total_value) AS sum_total_value, COUNT(*) AS row_count FROM
order_lines WHERE order_id = 1 GROUP BY order_id
-----------------------------------------------------------------------------------------
-------------------------
| Id | Operation | Name | Rows | Bytes |
Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
-------------------------
| 0 | SELECT STATEMENT | | | |
19 (100)| |
| 1 | VIEW | | 1001 | 52052 |
19 (27)| 00:00:01 |
| 2 | UNION-ALL | | | |
|* 3 | FILTER | | | |
|* 4 | HASH JOIN OUTER | | 1 | 33 |
9 (23)| 00:00:01 |
|* 5 | MAT_VIEW ACCESS FULL | ORDER_SUMMARY_RTMV | 1 | 17 |
4 (0)| 00:00:01 |
| 6 | VIEW | | 1 | 16 |
5 (40)| 00:00:01 |
| 7 | HASH GROUP BY | | 1 | 39 |
5 (40)| 00:00:01 |
| 8 | VIEW | | 1 | 39 |
4 (25)| 00:00:01 |
| 9 | RESULT CACHE | dyqrs00u554qffvsw6akf32p2p | | |
|* 10 | VIEW | | 1 | 103 |
4 (25)| 00:00:01 |
| 11 | WINDOW SORT | | 1 | 194 |
4 (25)| 00:00:01 |
|* 12 | TABLE ACCESS FULL | MLOG$_ORDER_LINES | 1 | 194 |
3 (0)| 00:00:01 |
| 13 | VIEW | | 1000 | 52000 |
10 (30)| 00:00:01 |
| 14 | UNION-ALL | | | |
|* 15 | FILTER | | | |
| 16 | NESTED LOOPS OUTER | | 999 | 94905 |
4 (25)| 00:00:01 |
| 17 | VIEW | | 1 | 78 |
4 (25)| 00:00:01 |
|* 18 | FILTER | | | |
66
| 19 | HASH GROUP BY | | 1 | 39 |
4 (25)| 00:00:01 |
|* 20 | VIEW | | 1 | 39 |
4 (25)| 00:00:01 |
| 21 | RESULT CACHE | dyqrs00u554qffvsw6akf32p2p | | |
|* 22 | VIEW | | 1 | 103 |
4 (25)| 00:00:01 |
| 23 | WINDOW SORT | | 1 | 194 |
4 (25)| 00:00:01 |
|* 24 | TABLE ACCESS FULL | MLOG$_ORDER_LINES | 1 | 194 |
3 (0)| 00:00:01 |
|* 25 | INDEX UNIQUE SCAN | I_SNAP$_ORDER_SUMMARY_RTMV | 999 | 16983 |
0 (0)| |
| 26 | NESTED LOOPS | | 1 | 98 |
6 (34)| 00:00:01 |
| 27 | VIEW | | 1 | 81 |
5 (40)| 00:00:01 |
| 28 | HASH GROUP BY | | 1 | 39 |
5 (40)| 00:00:01 |
| 29 | VIEW | | 1 | 39 |
4 (25)| 00:00:01 |
| 30 | RESULT CACHE | dyqrs00u554qffvsw6akf32p2p | | |
|* 31 | VIEW | | 1 | 103 |
4 (25)| 00:00:01 |
| 32 | WINDOW SORT | | 1 | 194 |
4 (25)| 00:00:01 |
|* 33 | TABLE ACCESS FULL | MLOG$_ORDER_LINES | 1 | 194 |
3 (0)| 00:00:01 |
|* 34 | MAT_VIEW ACCESS BY INDEX ROWID| ORDER_SUMMARY_RTMV | 1 | 17 |
1 (0)| 00:00:01 |
|* 35 | INDEX UNIQUE SCAN | I_SNAP$_ORDER_SUMMARY_RTMV | 1 | |
0 (0)| |
-----------------------------------------------------------------------------------------
-------------------------
3 -
filter("AV$0"."OJ_MARK" IS NULL)
4 -
access(SYS_OP_MAP_NONNULL("ORDER_ID")=SYS_OP_MAP_NONNULL("AV$0"."GB0"))
5 -
filter("ORDER_SUMMARY_RTMV"."ORDER_ID"=1)
10 -
filter((("MAS$"."OLD_NEW$$"='N' AND "MAS$"."SEQ$$"="MAS$"."MAXSEQ$$") OR
(INTERNAL_FUNCTION("MAS$"."OLD_NEW$$") AND
"MAS$"."SEQ$$"="MAS$"."MINSEQ$$")))
12 - filter("MAS$"."SNAPTIME$$">TO_DATE(' 2017-07-16 19:11:21', 'syyyy-mm-dd
hh24:mi:ss'))
15 - filter(CASE WHEN ROWID IS NOT NULL THEN 1 ELSE NULL END IS NULL)
18 - filter(SUM(1)>0)
20 - filter("MAS$"."ORDER_ID"=1)
22 - filter((("MAS$"."OLD_NEW$$"='N' AND "MAS$"."SEQ$$"="MAS$"."MAXSEQ$$") OR
(INTERNAL_FUNCTION("MAS$"."OLD_NEW$$") AND
"MAS$"."SEQ$$"="MAS$"."MINSEQ$$")))
24 - filter("MAS$"."SNAPTIME$$">TO_DATE(' 2017-07-16 19:11:21', 'syyyy-mm-dd
hh24:mi:ss'))
25 - access("ORDER_SUMMARY_RTMV"."SYS_NC00005$"=SYS_OP_MAP_NONNULL("AV$0"."GB0"))
31 - filter((("MAS$"."OLD_NEW$$"='N' AND "MAS$"."SEQ$$"="MAS$"."MAXSEQ$$") OR
(INTERNAL_FUNCTION("MAS$"."OLD_NEW$$") AND
"MAS$"."SEQ$$"="MAS$"."MINSEQ$$")))
33 - filter("MAS$"."SNAPTIME$$">TO_DATE(' 2017-07-16 19:11:21', 'syyyy-mm-dd
hh24:mi:ss'))
67
34 - filter(("ORDER_SUMMARY_RTMV"."ORDER_ID"=1 AND
"ORDER_SUMMARY_RTMV"."ROW_COUNT"+"AV$0"."D0">0))
35 - access("ORDER_SUMMARY_RTMV"."SYS_NC00005$"=SYS_OP_MAP_NONNULL("AV$0"."GB0"))
9 -
21 -
30 -
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- this is an adaptive plan
83 rows selected.
SQL>
We can see the row count is now 96 and the execution plan includes additional work to complete the wind-
forward.
SELECT order_id,
sum_line_qty,
sum_total_value,
row_count
FROM order_summary_rtmv
WHERE order_id = 1;
SQL>
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------
--
SQL_ID 8tq6wzmccuzfm, child number 0
-------------------------------------
SELECT order_id, sum_line_qty, sum_total_value,
row_count FROM order_summary_rtmv WHERE order_id = 1
68
-----------------------------------------------------------------------------------------
--
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
--
| 0 | SELECT STATEMENT | | | | 4 (100)| |
|* 1 | MAT_VIEW ACCESS FULL| ORDER_SUMMARY_RTMV | 1 | 17 | 4 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------
--
1 - filter("ORDER_ID"=1)
19 rows selected.
SQL>
The FRESH_VM hint tells Oracle we want to take advantage of the real-time functionality when doing a direct
query against the materialized view, which is why we see a row count of 96 again.
SQL>
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------
-------------------------
SQL_ID 0pqhrf8c5kbgz, child number 0
-------------------------------------
SELECT /*+ FRESH_MV */ order_id, sum_line_qty,
sum_total_value, row_count FROM order_summary_rtmv WHERE
order_id = 1
-----------------------------------------------------------------------------------------
-------------------------
| Id | Operation | Name | Rows | Bytes |
Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
-------------------------
69
| 0 | SELECT STATEMENT | | | |
20 (100)| |
| 1 | VIEW | | 1001 | 52052 |
20 (30)| 00:00:01 |
| 2 | UNION-ALL | | | |
|* 3 | FILTER | | | |
|* 4 | HASH JOIN OUTER | | 1 | 33 |
9 (23)| 00:00:01 |
|* 5 | MAT_VIEW ACCESS FULL | ORDER_SUMMARY_RTMV | 1 | 17 |
4 (0)| 00:00:01 |
| 6 | VIEW | | 1 | 16 |
5 (40)| 00:00:01 |
| 7 | HASH GROUP BY | | 1 | 39 |
5 (40)| 00:00:01 |
| 8 | VIEW | | 1 | 39 |
4 (25)| 00:00:01 |
| 9 | RESULT CACHE | dyqrs00u554qffvsw6akf32p2p | | |
|* 10 | VIEW | | 1 | 103 |
4 (25)| 00:00:01 |
| 11 | WINDOW SORT | | 1 | 194 |
4 (25)| 00:00:01 |
|* 12 | TABLE ACCESS FULL | MLOG$_ORDER_LINES | 1 | 194 |
3 (0)| 00:00:01 |
| 13 | VIEW | | 1000 | 52000 |
11 (37)| 00:00:01 |
| 14 | UNION-ALL | | | |
|* 15 | FILTER | | | |
| 16 | NESTED LOOPS OUTER | | 999 | 94905 |
5 (40)| 00:00:01 |
| 17 | VIEW | | 1 | 78 |
5 (40)| 00:00:01 |
|* 18 | FILTER | | | |
| 19 | HASH GROUP BY | | 1 | 39 |
5 (40)| 00:00:01 |
|* 20 | VIEW | | 1 | 39 |
4 (25)| 00:00:01 |
| 21 | RESULT CACHE | dyqrs00u554qffvsw6akf32p2p | | |
|* 22 | VIEW | | 1 | 103 |
4 (25)| 00:00:01 |
| 23 | WINDOW SORT | | 1 | 194 |
4 (25)| 00:00:01 |
|* 24 | TABLE ACCESS FULL | MLOG$_ORDER_LINES | 1 | 194 |
3 (0)| 00:00:01 |
|* 25 | INDEX UNIQUE SCAN | I_SNAP$_ORDER_SUMMARY_RTMV | 999 | 16983 |
0 (0)| |
| 26 | NESTED LOOPS | | 1 | 98 |
6 (34)| 00:00:01 |
| 27 | VIEW | | 1 | 81 |
5 (40)| 00:00:01 |
| 28 | HASH GROUP BY | | 1 | 39 |
5 (40)| 00:00:01 |
| 29 | VIEW | | 1 | 39 |
4 (25)| 00:00:01 |
| 30 | RESULT CACHE | dyqrs00u554qffvsw6akf32p2p | | |
|* 31 | VIEW | | 1 | 103 |
4 (25)| 00:00:01 |
| 32 | WINDOW SORT | | 1 | 194 |
4 (25)| 00:00:01 |
|* 33 | TABLE ACCESS FULL | MLOG$_ORDER_LINES | 1 | 194 |
3 (0)| 00:00:01 |
70
|* 34 | MAT_VIEW ACCESS BY INDEX ROWID| ORDER_SUMMARY_RTMV | 1 | 17 |
1 (0)| 00:00:01 |
|* 35 | INDEX UNIQUE SCAN | I_SNAP$_ORDER_SUMMARY_RTMV | 1 | |
0 (0)| |
-----------------------------------------------------------------------------------------
-------------------------
3 -
filter("AV$0"."OJ_MARK" IS NULL)
4 -
access(SYS_OP_MAP_NONNULL("ORDER_ID")=SYS_OP_MAP_NONNULL("AV$0"."GB0"))
5 -
filter("ORDER_SUMMARY_RTMV"."ORDER_ID"=1)
10 -
filter((("MAS$"."OLD_NEW$$"='N' AND "MAS$"."SEQ$$"="MAS$"."MAXSEQ$$") OR
(INTERNAL_FUNCTION("MAS$"."OLD_NEW$$") AND
"MAS$"."SEQ$$"="MAS$"."MINSEQ$$")))
12 - filter("MAS$"."SNAPTIME$$">TO_DATE(' 2017-07-16 19:11:21', 'syyyy-mm-dd
hh24:mi:ss'))
15 - filter(CASE WHEN ROWID IS NOT NULL THEN 1 ELSE NULL END IS NULL)
18 - filter(SUM(1)>0)
20 - filter("MAS$"."ORDER_ID"=1)
22 - filter((("MAS$"."OLD_NEW$$"='N' AND "MAS$"."SEQ$$"="MAS$"."MAXSEQ$$") OR
(INTERNAL_FUNCTION("MAS$"."OLD_NEW$$") AND
"MAS$"."SEQ$$"="MAS$"."MINSEQ$$")))
24 - filter("MAS$"."SNAPTIME$$">TO_DATE(' 2017-07-16 19:11:21', 'syyyy-mm-dd
hh24:mi:ss'))
25 - access("ORDER_SUMMARY_RTMV"."SYS_NC00005$"=SYS_OP_MAP_NONNULL("AV$0"."GB0"))
31 - filter((("MAS$"."OLD_NEW$$"='N' AND "MAS$"."SEQ$$"="MAS$"."MAXSEQ$$") OR
(INTERNAL_FUNCTION("MAS$"."OLD_NEW$$") AND
"MAS$"."SEQ$$"="MAS$"."MINSEQ$$")))
33 - filter("MAS$"."SNAPTIME$$">TO_DATE(' 2017-07-16 19:11:21', 'syyyy-mm-dd
hh24:mi:ss'))
34 - filter(("ORDER_SUMMARY_RTMV"."ORDER_ID"=1 AND
"ORDER_SUMMARY_RTMV"."ROW_COUNT"+"AV$0"."D0">0))
35 - access("ORDER_SUMMARY_RTMV"."SYS_NC00005$"=SYS_OP_MAP_NONNULL("AV$0"."GB0"))
9 -
21 -
30 -
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- this is an adaptive plan
83 rows selected.
SQL>
71
Authid current_user
The authid current_user is used when you want a piece of code (PL/SQL) to execute with the privileges of the
current user, and NOT the user ID that created the procedure. This is termed a "invoker rights", the opposite of
"definer rights".
In the same sense, the authid current_user is the reverse of the "grant execute" where the current user does not
matter, the privileges of the creating user are used.
PL/SQL, by default, run with the privileges of the schema within which they are created no matter who invokes
the procedure. In order for a PL/SQL package to run with invokers rights AUTHID CURRENT_USER has to
be explicitly written into the package.
The authid current_user clause tells the kernel that any methods that may be used in the type specification (in
the above example, none) should execute with the privilege of the executing user, not the owner.
The default option is authid definer, which would correspond to the behavior in pre-Oracle8i releases, where
the method would execute with the privileges of the user creating the type.
See this example of authid current_user to understand how this syntax causes invoker rights, which, in turn,
changes the behavior of the PL/SQL code.
WARNING: Writing PL/SQL code with the default authid definer, can facilitate SQL injection attacks, because
an intruder would get privileges that they would not get if they used authid current_user.
72
Passing Large Data Structures with PL/SQL
NOCOPY
The PL/SQL runtime engine has two different methods for passing parameter values between stored procedures
and functions, by value and by reference.
When a parameter is passed by value the PL/SQL runtime engine copies the actual value of the parameter into
the formal parameter. Any changes made to the parameter inside the procedure has no effect on the values of
the variables that were passed to the procedure from outside.
When a parameter is passed by reference the runtime engine sets up the procedure call so that both the actual
and the formal parameters point (reference) the same memory location that holds the value of the parameter.
By default OUT and IN OUT parameters are passed by value and IN parameters are passed by reference. When
an OUT or IN OUT parameter is modified inside the procedure the procedure actually only modifies a copy of
the parameter value. Only when the procedure has finished without exception is the result value copied back to
the formal parameter.
Now, if you pass a large collection as an OUT or an IN OUT parameter then it will be passed by value, in other
words the entire collection will be copied to the formal parameter when entering the procedure and back again
when exiting the procedure. If the collection is large this can lead to unnecessary CPU and memory
consumption.
The NOCOPY hint alleviates this problem because you can use it to instruct the runtime engine to try to pass
OUT or IN OUT parameters by reference instead of by value. For example:
procedure get_customer_orders(
p_customer_id in number,
p_orders out nocopy orders_coll
);
theorders orders_coll;
get_customer_orders(124, theorders);
In the absence of the NOCOPY hint the entire orders collection would have been copied into the theorders
variable upon exit from the procedure. Instead the collection is now passed by reference.
Keep in mind, however, that there is a downside to using NOCOPY. When you pass parameters to a procedure
by reference then any modifications you perform on the parameters inside the procedure is done on the same
memory location as the actual parameter, so the modifications are visible. In other words, there is no way to
?undo? or ?rollback? these modifications, even when an exception is raised midway. So if an exception is
raised inside the procedure the value of the parameter is ?undefined? and cannot be trusted.
Consider our get_customer_orders example. If the p_orders parameter was half-filled with orders when an
exception was raised, then upon exit our theorders variable will also be half-filled because it points to the same
memory location as the p_orders parameter. This downside is most problematic for IN OUT parameters
73
because if an exception occurs midway then not only is the output garbage, but you?ve also made the input
garbage.
To sum up, a NOCOPY hint can offer a small performance boost, but you must be careful and know how it
affects program behavior, in particular exception handling.
NOCOPY Clause
The final point to cover in passing variables is the NOCOPY clause . When a parameter is passed as an IN
variable, it is passed by reference. Since it will not change, PL/SQL uses the passed variable in the
procedure/function. When variables are passed in OUT or INOUT mode, a new variable is define, and the value
is copied to the passed variable when the procedure ends. If the variable is a large structure such as a PL/SQL
table or an array, the application could see a performance degradation cause by copying this structure.
The NOCOPY clause tells to PL/SQL engine to pass the variable by reference, thus avoiding the cost of
copying the variable at the end of the procedure. The PL/SQL engine has requirements that must be met before
passing the variable by reference and if those requirements are not met, the NOCOPY clause will simply be
ignored by the PL/SQL engine.
Important note: If an OUT or INOUT variable is passed by reference (NOCOPY) and the procedure
terminates due to an unhandled exception (ends abnormally), the value of the referenced variable may no
longer be valid.
Both stored procedures and functions are passed variables, the only difference is that a function can only be
passed IN variables because a function returns a value.
74
Open Discussion Subjects
Select from more than one partition with local index
Tablespace for each index tip
Tablespaces with NOLOGGING feature
Update index fields tip
Unusable index tip
Parallel index
Parallel hint tips
Table Access BY USER ROWID
Hash table joins VS nested Loops table joins
Truncate table drop all storage
SET Operators VS (in and not in) and (exists and not exists)
Multiset with collection (Map PK)
Object type Collections and speed and ram
Forall Values of and indices of
Regular expressions
Dynamic plsql
With clause
Pin package tip
Clear session memory tip
DBMS_SCHEDULER
Virtual Columns
75