Oracle PL/SQL 優化與調整 -- Bulk 說明
一. Bulk 概述
本來只想測試一下Bulk Collect 和update效能的,但發現Bulk 的東西還是很多的,在OTN上搜了一些,整理如下。
1.1 Bulk Binding 和 Bulk SQL
From: http://download.oracle.com/docs/cd/E11882_01/appdev.112/e17125/adfns_packages.htm#ADFNS343
Oracle Database uses two engines to run PL/SQL blocks and subprograms. The PL/SQL engine runs procedural statements, whilethe SQL engine runs SQL statements. During execution, every SQL statement causes a context switch between the two engines, resulting in performance overhead.
-- Oracle 使用2個引擎來執行SQL和程式碼塊:SQL 引擎和PL/SQL 引擎,SQL 語句會導致在兩個引擎之間進行context switch,從而影響效能。
Performance can be improved substantially by minimizing the number of context switches required to run a particular block or subprogram. When a SQL statement runs inside a loop that uses collection elements as bind variables, the large number of context switches required by the block can cause poor performance. Collections include:
(1)Varrays
(2)Nested tables
(3)Index-by tables
(4)Host arrays
-- 從本質上講,使用特殊的block 或者 subprogram 來降低context switches 可以提高效能。 當SQL 語句在loop 內使用 collection elements作為bind variables 來執行時,就會產生大量的context switches 。
Bulk SQL minimizes the performance overhead of the communication between PL/SQL and SQL.
PL/SQL and SQL communicate as follows:
To run a SELECT INTO or DML statement, the PL/SQL engine sends the query or DML statement to the SQL engine. The SQL engine runs the query or DML statement and returns the result to the PL/SQL engine.
-- PL/SQL 和 SQL 引擎的交流方式
The PL/SQL features that comprise bulk SQL are the FORALL statement and the BULK COLLECT clause.
The FORALL statement sends DML statements from PL/SQL to SQL in batches rather than one at a time.
The BULK COLLECT clause returns results from SQL to PL/SQL in batches rather than one at a time. If a query or DML statement affects four or more database rows, then bulk SQL can significantly improve performance.
Assigning values to PL/SQL variables that appear in SQL statements is called binding.
PL/SQL binding operations fall into these categories:
Binding Category | When This Binding Occurs |
When an INSERT or UPDATE statement stores a PL/SQL or host variable in the database | |
When the RETURNING INTO clause of an INSERT, UPDATE, or DELETE statement assigns a database value to a PL/SQL or host variable | |
When a SELECT or FETCH statement assigns a database value to a PL/SQL or host variable |
For in-binds and out-binds, bulk SQL uses bulk binding; that is, it binds an entire collection of values at once.
For a collection of n elements, bulk SQL uses a single operation to perform. the equivalent of n SELECT INTO or DML statements. A query that uses bulk SQL can return any number of rows, without using a FETCH statement for each one.
Binding is the assignment of values to PL/SQL variables in SQL statements. Bulk binding is binding an entire collection at once. Bulk binds pass the entire collection back and forth between the two engines in a single operation.
--Binding 是在SQL 語句裡分配一個value 給PL/SQL 變數
--Bulk Binding 是一次分配所有的資料,然後通過這個entire collection,在一個操作就可以完成兩個引擎處理。
Typically, using bulk binds improves performance for SQL statements that affect four or more database rows. The more rows affected by a SQL statement, the greater the performance gain from bulk binds.
注意:
Parallel DML statements are disabled with bulk binds and bulk SQL.
並行的DML 操作會禁用bulk binds 和 bulk SQL.
Note:
This section provides an overview of bulk binds to help you decide whether to use them in your PL/SQL applications. For detailed information about using bulk binds, including ways to handle exceptions that occur in the middle of a bulk bind operation, see Oracle Database PL/SQL Language Reference.
Consider using bulk binds to improve the performance of:
DML Statements that Reference Collections
SELECT Statements that Reference Collections
FOR Loops that Reference Collections and Return DML
1.2.1 DML Statements that Reference Collections
A bulk bind, which uses the FORALL keyword, can improve the performance of INSERT, UPDATE, or DELETE statements that reference collection elements.
The PL/SQL block in Example 6-9 increases the salary for employees whose manager's ID number is 7902, 7698, or 7839, with and without bulk binds. Without bulk bind, PL/SQL sends a SQL statement to the SQL engine for each updated employee, leading to context switches that slow performance.
Example 6-9 DML Statements that Reference Collections
declare
type numlist is varray (100) of number;
id numlist := numlist(7902, 7698, 7839);
begin
-- Efficient method, using bulk bind:
forall i in id.first..id.last
update employees
set salary = 1.1 * salary
where manager_id = id(i);
-- Slower method:
for i in id.first..id.last loop
update employees
set salary = 1.1 * salary
where manager_id = id(i);
end loop;
end;
/
1.2.2 SELECT Statements that Reference Collections
The BULK COLLECT INTO clause can improve the performance of queries that reference collections. You can use BULK COLLECT INTO with tables of scalar values, or tables of %TYPE values.
The PL/SQL block in Example 6-10 queries multiple values into PL/SQL tables, with and without bulk binds. Without bulk bind, PL/SQL sends a SQL statement to the SQL engine for each selected employee, leading to context switches that slow performance.
Example 6-10 SELECT Statements that Reference Collections
declare
type var_tab is table of varchar2(20)
index by pls_integer;
empno var_tab;
ename var_tab;
counter number;
cursor c is
select employee_id, last_name
from employees
where manager_id = 7698;
begin
-- Efficient method, using bulk bind:
select employee_id, last_name bulk collect
into empno, ename
from employees
where manager_id = 7698;
-- Slower method:
counter := 1;
for rec in c loop
empno(counter) := rec.employee_id;
ename(counter) := rec.last_name;
counter := counter + 1;
end loop;
end;
/
1.2.3 FOR Loops that Reference Collections and Return DML
You can use the FORALL keyword with the BULK COLLECT INTO keywords to improve the performance of FOR loops that reference collections and return DML.
The PL/SQL block in Example 6-11 updates the EMPLOYEES table by computing bonuses for a collection of employees. Then it returns the bonuses in a column called bonus_list_inst. The actions are performed with and without bulk binds. Without bulk bind, PL/SQL sends a SQL statement to the SQL engine for each updated employee, leading to context switches that slow performance.
Example 6-11 FOR Loops that Reference Collections and Return DML
declare
type emp_list is varray(100) of employees.employee_id%type;
empids emp_list := emp_list(182, 187, 193, 200, 204, 206);
type bonus_list is table of employees.salary%type;
bonus_list_inst bonus_list;
begin
-- Efficient method, using bulk bind:
forall i in empids.first..empids.last
update employees
set salary = 0.1 * salary
where employee_id = empids(i)
returning salary bulk collect into bonus_list_inst;
-- Slower method:
for i in empids.first..empids.last loop
update employees
set salary = 0.1 * salary
where employee_id = empids(i)
returning salary into bonus_list_inst(i);
end loop;
end;
/
A trigger is a special kind of PL/SQL anonymous block. You can define triggers to fire before or after SQL statements, either on a statement level or for each row that is affected. You can also define INSTEAD OF triggers or system triggers (triggers on DATABASE and SCHEMA).
二. 有關Bulk SQL 和 Bulk Binding 的更多示例
From:
Bulk SQL and Bulk Binding
http://download.oracle.com/docs/cd/E11882_01/appdev.112/e17126/tuning.htm
The FORALL statement, a feature of bulk SQL, sends DML statements from PL/SQL to SQL in batches rather than one at a time.
To understand the FORALL statement, first consider the FOR LOOP statement in Example 12-7. It sends these DML statements from PL/SQL to SQL one at a time:
delete from employees_temp where department_id = depts(10);
delete from employees_temp where department_id = depts(30);
delete from employees_temp where department_id = depts(70);
Example 12-7 DELETE Statement in FOR LOOP Statement
DROP TABLE employees_temp;
CREATE TABLE employees_temp AS SELECT * FROM employees;
DECLARE
TYPE NumList IS VARRAY(20) OF NUMBER;
depts NumList := NumList(10, 30, 70); -- department numbers
BEGIN
FOR i IN depts.FIRST..depts.LAST LOOP
DELETE FROM employees_temp
WHERE department_id = depts(i);
END LOOP;
END;
/
Now consider the FORALL statement in Example 12-8. It sends the same three DML statements from PL/SQL to SQL as a batch.
Example 12-8 DELETE Statement in FORALL Statement
DROP TABLE employees_temp;
CREATE TABLE employees_temp AS SELECT * FROM employees;
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;
/
A FORALL statement is usually much faster than an equivalent FOR LOOP statement. However, a FOR LOOP statement can contain multiple DML statements, while a FORALL statement can contain only one.
--FORALL 只能包含一條DML 語句,而FOR LOOP 可以包含多條
The batch of DML statements that a FORALL statement sends to SQL differ only in their VALUES and WHERE clauses. The values in those clauses must come from existing, populated collections.
Note:
The DML statement in a FORALL statement can reference multiple collections, but performance benefits apply only to collection references that use the FORALL index variable as an index.
Example 12-9 inserts the same collection elements into two database tables, using a FOR LOOP statement for the first table and a FORALL statement for the second table and showing how long each statement takes. (Times vary from run to run.)
Example 12-9 Time Difference for INSERT Statement in FOR LOOP and FORALL Statements
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)
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: 2.16
FORALL: .11
PL/SQL procedure successfully completed.
In Example 12-10, the FORALL statement applies to a subset of a collection.
Example 12-10 FORALL Statement for Subset of Collection
DROP TABLE employees_temp;
CREATE TABLE employees_temp AS SELECT * FROM employees;
DECLARE
TYPE NumList IS VARRAY(10) OF NUMBER;
depts NumList := NumList(5,10,20,30,50,55,57,60,70,75);
BEGIN
FORALL j IN 4..7
DELETE FROM employees_temp WHERE department_id = depts(j);
END;
/
2.2 FORALL Statements for Sparse Collections
If the FORALL statement bounds clause references a sparse collection, then specify only existing index values, using either the INDICES OF or VALUES OF clause. You can use INDICES OF for any collection except an associative array indexed by string. You can use VALUES OF only for a collection of PLS_INTEGER elements indexed by PLS_INTEGER.
A collection of PLS_INTEGER elements indexed by PLS_INTEGER can be an index collection; that is, a collection of pointers to elements of another collection (the indexed collection).
Index collections are useful for processing different subsets of the same collection with different FORALL statements.Instead of copying elements of the original collection into new collections that represent the subsets (which can use significant time and memory), represent each subset with an index collection and then use each index collection in the VALUES OF clause of a different FORALL statement.
Example 12-11 uses a FORALL statement with the INDICES OF clause to populate a table with the elements of a sparse collection. Then it uses two FORALL statements with VALUES OF clauses to populate two tables with subsets of a collection.
Example 12-11 FORALL Statements for Sparse Collection and Its Subsets
DROP TABLE valid_orders;
CREATE TABLE valid_orders (
cust_name VARCHAR2(32),
amount NUMBER(10,2)
);
DROP TABLE big_orders;
CREATE TABLE big_orders AS
SELECT * FROM valid_orders
WHERE 1 = 0;
DROP TABLE rejected_orders;
CREATE TABLE rejected_orders AS
SELECT * FROM valid_orders
WHERE 1 = 0;
DECLARE
SUBTYPE cust_name IS valid_orders.cust_name%TYPE;
TYPE cust_typ IS TABLE OF cust_name;
cust_tab cust_typ; -- Collection of customer names
SUBTYPE order_amount IS valid_orders.amount%TYPE;
TYPE amount_typ IS TABLE OF NUMBER;
amount_tab amount_typ; -- Collection of order amounts
TYPE index_pointer_t IS TABLE OF PLS_INTEGER;
/* Collections for pointers to elements of cust_tab collection
(to represent two subsets of cust_tab): */
big_order_tab index_pointer_t := index_pointer_t();
rejected_order_tab index_pointer_t := index_pointer_t();
PROCEDURE populate_data_collections IS
BEGIN
cust_tab := cust_typ(
'Company1','Company2','Company3','Company4','Company5'
);
amount_tab := amount_typ(5000.01, 0, 150.25, 4000.00, NULL);
END;
BEGIN
populate_data_collections;
DBMS_OUTPUT.PUT_LINE ('--- Original order data ---');
FOR i IN 1..cust_tab.LAST LOOP
DBMS_OUTPUT.PUT_LINE (
'Customer #' || i || ', ' || cust_tab(i) || ': $' || amount_tab(i)
);
END LOOP;
-- Delete invalid orders:
FOR i IN 1..cust_tab.LAST LOOP
IF amount_tab(i) IS NULL OR amount_tab(i) = 0 THEN
cust_tab.delete(i);
amount_tab.delete(i);
END IF;
END LOOP;
-- cust_tab is now a sparse collection.
DBMS_OUTPUT.PUT_LINE ('--- Order data with invalid orders deleted ---');
FOR i IN 1..cust_tab.LAST LOOP
IF cust_tab.EXISTS(i) THEN
DBMS_OUTPUT.PUT_LINE (
'Customer #' || i || ', ' || cust_tab(i) || ': $' || amount_tab(i)
);
END IF;
END LOOP;
-- Using sparse collection, populate valid_orders table:
FORALL i IN INDICES OF cust_tab
INSERT INTO valid_orders (cust_name, amount)
VALUES (cust_tab(i), amount_tab(i));
populate_data_collections; -- Restore original order data
-- cust_tab is a dense collection again.
/* Populate collections of pointers to elements of cust_tab collection
(which represent two subsets of cust_tab): */
FOR i IN cust_tab.FIRST .. cust_tab.LAST LOOP
IF amount_tab(i) IS NULL OR amount_tab(i) = 0 THEN
rejected_order_tab.EXTEND;
rejected_order_tab(rejected_order_tab.LAST) := i;
END IF;
IF amount_tab(i) > 2000 THEN
big_order_tab.EXTEND;
big_order_tab(big_order_tab.LAST) := i;
END IF;
END LOOP;
/* Using each subset in a different FORALL statement,
populate rejected_orders and big_orders tables: */
FORALL i IN VALUES OF rejected_order_tab
INSERT INTO rejected_orders (cust_name, amount)
VALUES (cust_tab(i), amount_tab(i));
FORALL i IN VALUES OF big_order_tab
INSERT INTO big_orders (cust_name, amount)
VALUES (cust_tab(i), amount_tab(i));
END;
/
2.3 Unhandled Exceptions in FORALL Statements
In a FORALL statement without the SAVE EXCEPTIONS clause, if one DML statement raises an unhandled exception, then PL/SQL stops the FORALL statement and rolls back all changes made by previous DML statements.
For example, the FORALL statement in Example 12-8 executes these DML statements in this order, unless one of them raises an unhandled exception:
DELETE FROM employees_temp WHERE department_id = depts(10);
DELETE FROM employees_temp WHERE department_id = depts(30);
DELETE FROM employees_temp WHERE department_id = depts(70);
If the third statement raises an unhandled exception, then PL/SQL rolls back the changes that the first and second statements made. If the second statement raises an unhandled exception, then PL/SQL rolls back the changes that the first statement made and never runs the third statement.
You can handle exceptions raised in a FORALL statement in either of these ways:
(1)As each exception is raised (see "Handling FORALL Exceptions Immediately")
(2)After the FORALL statement completes execution, by including the SAVE EXCEPTIONS clause (see "Handling FORALL Exceptions After FORALL Statement Completes")
2.4 Handling FORALL Exceptions Immediately
To handle exceptions raised in a FORALL statement immediately, omit the SAVE EXCEPTIONS clause and write the appropriate exception handlers. (For information about exception handlers, see Chapter 11, "PL/SQL Error Handling.") If one DML statement raises a handled exception, then PL/SQL rolls back the changes made by that statement, but does not roll back changes made by previous DML statements.
In Example 12-12, the FORALL statement is designed to run three UPDATE statements. However, the second one raises an exception. An exception handler handles the exception, displaying the error message and committing the change made by the first UPDATE statement. The third UPDATE statement never runs.
Example 12-12 Handling FORALL Exceptions Immediately
DROP TABLE emp_temp;
CREATE TABLE emp_temp (
deptno NUMBER(2),
job VARCHAR2(18)
);
CREATE OR REPLACE PROCEDURE p AUTHID DEFINER AS
TYPE NumList IS TABLE OF NUMBER;
depts NumList := NumList(10, 20, 30);
error_message VARCHAR2(100);
BEGIN
-- Populate table:
INSERT INTO emp_temp (deptno, job) VALUES (10, 'Clerk');
INSERT INTO emp_temp (deptno, job) VALUES (20, 'Bookkeeper');
INSERT INTO emp_temp (deptno, job) VALUES (30, 'Analyst');
COMMIT;
-- Append 9-character string to each job:
FORALL j IN depts.FIRST..depts.LAST
UPDATE emp_temp SET job = job || ' (Senior)'
WHERE deptno = depts(j);
EXCEPTION
WHEN OTHERS THEN
error_message := SQLERRM;
DBMS_OUTPUT.PUT_LINE (error_message);
COMMIT; -- Commit results of successful updates
RAISE;
END;
/
Result:
Procedure created.
Invoke procedure:
BEGIN
p;
END;
/
Result:
ORA-12899: value too large for column "HR"."EMP_TEMP"."JOB" (actual: 19,
maximum: 18)
ORA-06512: at "HR.P", line 27
ORA-06512: at line 2
PL/SQL procedure successfully completed.
Query:
SELECT * FROM emp_temp;
Result:
DEPTNO JOB
---------- ------------------
10 Clerk (Senior)
20 Bookkeeper
30 Analyst
3 rows selected.
2.5 Handling FORALL Exceptions After FORALL Statement Completes
To allow a FORALL statement to continue even if some of its DML statements fail, include the SAVE EXCEPTIONS clause. When a DML statement fails, PL/SQL does not raise an exception; instead, it saves information about the failure. After the FORALL statement completes, PL/SQL raises a single exception for the FORALL statement (ORA-24381). In the exception handler for ORA-24381, you can get information about each individual DML statement failure from the implicit cursor attribute SQL%BULK_EXCEPTIONS.
SQL%BULK_EXCEPTIONS is like an associative array of information about the DML statements that failed during the most recently run FORALL statement.
SQL%BULK_EXCEPTIONS.COUNT is the number of DML statements that failed. If SQL%BULK_EXCEPTIONS.COUNT is not zero, then for each index value i from 1 through SQL%BULK_EXCEPTIONS.COUNT:
(1)SQL%BULK_EXCEPTIONS(i).ERROR_INDEX is the number of the DML statement that failed.
(2)SQL%BULK_EXCEPTIONS(i).ERROR_CODE is the Oracle Database error code for the failure.
For example, if a FORALL SAVE EXCEPTIONS statement runs 100 DML statements, and the tenth and sixty-fourth ones fail with error codes ORA-12899 and ORA-19278, respectively, then:
SQL%BULK_EXCEPTIONS.COUNT = 2
SQL%BULK_EXCEPTIONS(1).ERROR_INDEX = 10
SQL%BULK_EXCEPTIONS(1).ERROR_CODE = 12899
SQL%BULK_EXCEPTIONS(2).ERROR_INDEX = 64
SQL%BULK_EXCEPTIONS(2).ERROR_CODE = 19278
Note:
After a FORALL statement without the SAVE EXCEPTIONS clause raises an exception, SQL%BULK_EXCEPTIONS.COUNT = 1.
With the error code, you can get the associated error message with the SQLERRM function (described in "SQLERRM Function"):
SQLERRM(-(SQL%BULK_EXCEPTIONS(i).ERROR_CODE))
However, the error message that SQLERRM returns excludes any substitution arguments (compare the error messages in Example 12-12 and Example 12-13).
Example 12-13 is like Example 12-12 except:
(1)The FORALL statement includes the SAVE EXCEPTIONS clause.
(2)The exception-handling part has an exception handler for ORA-24381, the internally defined exception that PL/SQL raises implicitly when a bulk operation raises and saves exceptions. The example gives ORA-24381 the user-defined name dml_errors.
(3)The exception handler for dml_errors uses SQL%BULK_EXCEPTIONS and SQLERRM (and some local variables) to show the error message and which statement, collection item, and string caused the error.
Example 12-13 Handling FORALL Exceptions After FORALL Statement Completes
CREATE OR REPLACE PROCEDURE p AUTHID DEFINER AS
TYPE NumList IS TABLE OF NUMBER;
depts NumList := NumList(10, 20, 30);
error_message VARCHAR2(100);
bad_stmt_no PLS_INTEGER;
bad_deptno emp_temp.deptno%TYPE;
bad_job emp_temp.job%TYPE;
dml_errors EXCEPTION;
PRAGMA EXCEPTION_INIT(dml_errors, -24381);
BEGIN
-- Populate table:
INSERT INTO emp_temp (deptno, job) VALUES (10, 'Clerk');
INSERT INTO emp_temp (deptno, job) VALUES (20, 'Bookkeeper');
INSERT INTO emp_temp (deptno, job) VALUES (30, 'Analyst');
COMMIT;
-- Append 9-character string to each job:
FORALL j IN depts.FIRST..depts.LAST SAVE EXCEPTIONS
UPDATE emp_temp SET job = job || ' (Senior)'
WHERE deptno = depts(j);
EXCEPTION
WHEN dml_errors THEN
FOR i IN 1..SQL%BULK_EXCEPTIONS.COUNT LOOP
error_message := SQLERRM(-(SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
DBMS_OUTPUT.PUT_LINE (error_message);
bad_stmt_no := SQL%BULK_EXCEPTIONS(i).ERROR_INDEX;
DBMS_OUTPUT.PUT_LINE('Bad statement #: ' || bad_stmt_no);
bad_deptno := depts(bad_stmt_no);
DBMS_OUTPUT.PUT_LINE('Bad department #: ' || bad_deptno);
SELECT job INTO bad_job FROM emp_temp WHERE deptno = bad_deptno;
DBMS_OUTPUT.PUT_LINE('Bad job: ' || bad_job);
END LOOP;
COMMIT; -- Commit results of successful updates
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Unrecognized error.');
RAISE;
END;
/
Result:
Procedure created.
Invoke procedure:
BEGIN
p;
END;
/
Result:
ORA-12899: value too large for column (actual: , maximum: )
Bad statement #: 2
Bad department #: 20
Bad job: Bookkeeper
PL/SQL procedure successfully completed.
Query:
SELECT * FROM emp_temp;
Result:
DEPTNO JOB
---------- ------------------
10 Clerk (Senior)
20 Bookkeeper
30 Analyst (Senior)
3 rows selected.
2.6 Sparse Collections and SQL%BULK_EXCEPTIONS
If the FORALL statement bounds clause references a sparse collection, then to find the collection element that caused a DML statement to fail, you must step through the elements one by one until you find the element whose index is SQL%BULK_EXCEPTIONS(i).ERROR_INDEX. Then, if the FORALL statement uses the VALUES OF clause to reference a collection of pointers into another collection, you must find the element of the other collection whose index is SQL%BULK_EXCEPTIONS(i).ERROR_INDEX.
2.7 Getting Number of Rows Affected by FORALL Statement
After a FORALL statement completes, you can get the number of rows that each DML statement affected from the implicit cursor attribute SQL%BULK_ROWCOUNT. (To get the total number of rows affected by the FORALL statement, use the implicit cursor attribute SQL%ROWCOUNT, described in "SQL%ROWCOUNT Attribute: How Many Rows Were Affected?".)
SQL%BULK_ROWCOUNT is like an associative array whose ith element is the number of rows affected by the ith DML statement in the most recently completed FORALL statement.
Example 12-14 uses SQL%BULK_ROWCOUNT to show how many rows each DELETE statement in the FORALL statement deleted and SQL%ROWCOUNT to show the total number of rows deleted.
Example 12-14 Showing Number of Rows Affected by Each DELETE in FORALL
DROP TABLE emp_temp;
CREATE TABLE emp_temp AS SELECT * FROM employees;
DECLARE
TYPE NumList IS TABLE OF NUMBER;
depts NumList := NumList(30, 50, 60);
BEGIN
FORALL j IN depts.FIRST..depts.LAST
DELETE FROM emp_temp WHERE department_id = depts(j);
FOR i IN depts.FIRST..depts.LAST LOOP
DBMS_OUTPUT.PUT_LINE (
'Statement #' || i || ' deleted ' ||
SQL%BULK_ROWCOUNT(i) || ' rows.'
);
END LOOP;
DBMS_OUTPUT.PUT_LINE('Total rows deleted: ' || SQL%ROWCOUNT);
END;
/
Result:
Statement #1 deleted 6 rows.
Statement #2 deleted 45 rows.
Statement #3 deleted 5 rows.
Total rows deleted: 56
Example 12-15 uses SQL%BULK_ROWCOUNT to show how many rows each INSERT SELECT construct in the FORALL statement inserted and SQL%ROWCOUNT to show the total number of rows inserted.
Example 12-15 Showing Number of Rows Affected by Each INSERT SELECT in FORALL
DROP TABLE emp_by_dept;
CREATE TABLE emp_by_dept AS
SELECT employee_id, department_id
FROM employees
WHERE 1 = 0;
DECLARE
TYPE dept_tab IS TABLE OF departments.department_id%TYPE;
deptnums dept_tab;
BEGIN
SELECT department_id BULK COLLECT INTO deptnums FROM departments;
FORALL i IN 1..deptnums.COUNT
INSERT INTO emp_by_dept (employee_id, department_id)
SELECT employee_id, department_id
FROM employees
WHERE department_id = deptnums(i)
ORDER BY department_id, employee_id;
FOR i IN 1..deptnums.COUNT LOOP
-- 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 280: inserted 0 records
Total records inserted: 106
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:
(1)SELECT INTO statement
(2)FETCH statement
(3)RETURNING INTO clause of:
(A)DELETE statement
(B)INSERT statement
(C)UPDATE statement
(D)EXECUTE IMMEDIATE statement
With the BULK COLLECT clause, each of the preceding statements retrieves an entire result set and stores it in one or morecollection 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 (if they are associative arrays) or nullness (if they are varrays or nested tables), as in Example 12-22.
2.9 SELECT INTO Statement with BULK COLLECT Clause
The SELECT INTO statement with the BULK COLLECT clause (also called the SELECT BULK COLLECT INTO statement) selects an entire result set into one or more collection variables. For more information, see "SELECT INTO Statement".
Caution:
The SELECT BULK COLLECT INTO statement is vulnerable to aliasing, which can cause unexpected results. For details, see"SELECT BULK COLLECT INTO Statements and Aliasing".
Example 12-16 uses a SELECT BULK COLLECT INTO statement to select two database columns into two collections (nested tables).
Example 12-16 Bulk-Selecting Two Database Columns into Two Nested Tables
DECLARE
TYPE NumTab IS TABLE OF employees.employee_id%TYPE;
TYPE NameTab IS TABLE OF employees.last_name%TYPE;
enums NumTab;
names NameTab;
PROCEDURE print_first_n (n POSITIVE) IS
BEGIN
IF enums.COUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE ('Collections are empty.');
ELSE
DBMS_OUTPUT.PUT_LINE ('First ' || n || ' employees:');
FOR i IN 1 .. n LOOP
DBMS_OUTPUT.PUT_LINE (
' Employee #' || enums(i) || ': ' || names(i));
END LOOP;
END IF;
END;
BEGIN
SELECT employee_id, last_name
BULK COLLECT INTO enums, names
FROM employees
ORDER BY employee_id;
print_first_n(3);
print_first_n(6);
END;
/
Example 12-17 uses a SELECT BULK COLLECT INTO statement to select a result set into a nested table of records.
Example 12-17 Bulk-Selecting into Nested Table of Records
DECLARE
CURSOR c1 IS
SELECT first_name, last_name, hire_date
FROM employees;
TYPE NameSet IS TABLE OF c1%ROWTYPE;
stock_managers NameSet; -- nested table of records
BEGIN
-- Assign values to nested table of records:
SELECT first_name, last_name, hire_date
BULK COLLECT INTO stock_managers
FROM employees
WHERE job_id = 'ST_MAN'
ORDER BY hire_date;
-- Print nested table of records:
FOR i IN stock_managers.FIRST .. stock_managers.LAST LOOP
DBMS_OUTPUT.PUT_LINE (
stock_managers(i).hire_date || ' ' ||
stock_managers(i).last_name || ', ' ||
stock_managers(i).first_name
);
END LOOP;END;
/
2.10 SELECT BULK COLLECT INTO Statements and Aliasing
In a statement of the form
SELECT column BULK COLLECT INTO collection FROM table ...
column and collection are analogous to IN NOCOPY and OUT NOCOPY subprogram parameters, respectively, and PL/SQL passes them by reference. As with subprogram parameters that are passed by reference, aliasing can cause unexpected results.
See Also:
"Subprogram Parameter Aliasing with Parameters Passed by Reference"
In Example 12-18, the intention is to select specific values from a collection, numbers1, and then store them in the same collection. The unexpected result is that all elements of numbers1 are deleted. For workarounds, see Example 12-19 and Example 12-20.
Example 12-18 SELECT BULK COLLECT INTO Statement with Unexpected Results
CREATE OR REPLACE TYPE numbers_type IS
TABLE OF INTEGER
/
CREATE OR REPLACE PROCEDURE p (i IN INTEGER) IS
numbers1 numbers_type := numbers_type(1,2,3,4,5);
BEGIN
DBMS_OUTPUT.PUT_LINE('Before SELECT statement');
DBMS_OUTPUT.PUT_LINE('numbers1.COUNT() = ' || numbers1.COUNT());
FOR j IN 1..numbers1.COUNT() LOOP
DBMS_OUTPUT.PUT_LINE('numbers1(' || j || ') = ' || numbers1(j));
END LOOP;
--Self-selecting BULK COLLECT INTO clause:
SELECT a.COLUMN_VALUE
BULK COLLECT INTO numbers1
FROM TABLE(numbers1) a
WHERE a.COLUMN_VALUE > p.i
ORDER BY a.COLUMN_VALUE;
DBMS_OUTPUT.PUT_LINE('After SELECT statement');
DBMS_OUTPUT.PUT_LINE('numbers1.COUNT() = ' || numbers1.COUNT());
END p;
/
Invoke p:
BEGIN
p(2);
END;
/
Result:
Before SELECT statement
numbers1.COUNT() = 5
numbers1(1) = 1
numbers1(2) = 2
numbers1(3) = 3
numbers1(4) = 4
numbers1(5) = 5
After SELECT statement
numbers1.COUNT() = 0
Invoke p:
BEGIN
p(10);
END;
/
Result:
Before SELECT statement
numbers1.COUNT() = 5
numbers1(1) = 1
numbers1(2) = 2
numbers1(3) = 3
numbers1(4) = 4
numbers1(5) = 5
After SELECT statement
numbers1.COUNT() = 0
Example 12-19 uses a cursor to achieve the result intended by Example 12-18.
Example 12-19 Cursor Workaround for Example 12-18
CREATE OR REPLACE TYPE numbers_type IS
TABLE OF INTEGER
/
CREATE OR REPLACE PROCEDURE p (i IN INTEGER) IS
numbers1 numbers_type := numbers_type(1,2,3,4,5);
CURSOR c IS
SELECT a.COLUMN_VALUE
FROM TABLE(numbers1) a
WHERE a.COLUMN_VALUE > p.i
ORDER BY a.COLUMN_VALUE;
BEGIN
DBMS_OUTPUT.PUT_LINE('Before FETCH statement');
DBMS_OUTPUT.PUT_LINE('numbers1.COUNT() = ' || numbers1.COUNT());
FOR j IN 1..numbers1.COUNT() LOOP
DBMS_OUTPUT.PUT_LINE('numbers1(' || j || ') = ' || numbers1(j));
END LOOP;
OPEN c;
FETCH c BULK COLLECT INTO numbers1;
CLOSE c;
DBMS_OUTPUT.PUT_LINE('After FETCH statement');
DBMS_OUTPUT.PUT_LINE('numbers1.COUNT() = ' || numbers1.COUNT());
IF numbers1.COUNT() > 0 THEN
FOR j IN 1..numbers1.COUNT() LOOP
DBMS_OUTPUT.PUT_LINE('numbers1(' || j || ') = ' || numbers1(j));
END LOOP;
END IF;
END p;
/
Invoke p:
BEGIN
p(2);
END;
/
Result:
Before FETCH statement
numbers1.COUNT() = 5
numbers1(1) = 1
numbers1(2) = 2
numbers1(3) = 3
numbers1(4) = 4
numbers1(5) = 5
After FETCH statement
numbers1.COUNT() = 3
numbers1(1) = 3
numbers1(2) = 4
numbers1(3) = 5
Example 12-20 selects specific values from a collection, numbers1, and then stores them in a different collection, numbers2.Example 12-20 runs faster than Example 12-19.
Example 12-20 Second Collection Workaround for Example 12-18
CREATE OR REPLACE TYPE numbers_type IS
TABLE OF INTEGER
/
CREATE OR REPLACE PROCEDURE p (i IN INTEGER) IS
numbers1 numbers_type := numbers_type(1,2,3,4,5);
numbers2 numbers_type := numbers_type(0,0,0,0,0);
BEGIN
DBMS_OUTPUT.PUT_LINE('Before SELECT statement');
DBMS_OUTPUT.PUT_LINE('numbers1.COUNT() = ' || numbers1.COUNT());
FOR j IN 1..numbers1.COUNT() LOOP
DBMS_OUTPUT.PUT_LINE('numbers1(' || j || ') = ' || numbers1(j));
END LOOP;
DBMS_OUTPUT.PUT_LINE('numbers2.COUNT() = ' || numbers2.COUNT());
FOR j IN 1..numbers2.COUNT() LOOP
DBMS_OUTPUT.PUT_LINE('numbers2(' || j || ') = ' || numbers2(j));
END LOOP;
SELECT a.COLUMN_VALUE
BULK COLLECT INTO numbers2 -- numbers2 appears here
FROM TABLE(numbers1) a -- numbers1 appears here
WHERE a.COLUMN_VALUE > p.i
ORDER BY a.COLUMN_VALUE;
DBMS_OUTPUT.PUT_LINE('After SELECT statement');
DBMS_OUTPUT.PUT_LINE('numbers1.COUNT() = ' || numbers1.COUNT());
IF numbers1.COUNT() > 0 THEN
FOR j IN 1..numbers1.COUNT() LOOP
DBMS_OUTPUT.PUT_LINE('numbers1(' || j || ') = ' || numbers1(j));
END LOOP;
END IF;
DBMS_OUTPUT.PUT_LINE('numbers2.COUNT() = ' || numbers2.COUNT());
IF numbers2.COUNT() > 0 THEN
FOR j IN 1..numbers2.COUNT() LOOP
DBMS_OUTPUT.PUT_LINE('numbers2(' || j || ') = ' || numbers2(j));
END LOOP;
END IF;
END p;
/
2.11 Row Limits for SELECT BULK COLLECT INTO Statements
A SELECT BULK COLLECT INTO statement that returns a large number of rows produces a large collection. To limit the number of rows and the collection size, use either the ROWNUM pseudocolumn (described in Oracle Database SQL Language Reference) or SAMPLE clause (described in Oracle Database SQL Language Reference).
In Example 12-21, the first SELECT BULK COLLECT INTO statement uses ROWNUM to limit the number of rows to 50, and the second SELECT BULK COLLECT INTO statement uses SAMPLE to limit the number of rows to approximately 10% of the total.
Example 12-21 Limiting Bulk Selection with ROWNUM and SAMPLE
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);
END;
/
2.12 Guidelines for Looping Through Collections
When a result set is stored in a collection, it is easy to loop through the rows and refer to different columns. This technique can be very fast, but also very memory-intensive. If you use it often:
(1)To loop once through the result set, use a cursor FOR LOOP (see "Query Result Set Processing With Cursor FOR LOOP Statements"). This technique avoids the memory overhead of storing a copy of the result set.
(2)Instead of looping through the result set to search for certain values or filter the results into a smaller set, do the searching or filtering in the query of the SELECT INTO statement.
For example, in simple queries, use WHERE clauses; in queries that compare multiple result sets, use set operators such as INTERSECT and MINUS. For information about set operators, see Oracle Database SQL Language Reference.
(3)Instead of looping through the result set and running another query for each result row, use a subquery in the query of the SELECT INTO statement (see "Query Result Set Processing with Subqueries").
(4)Instead of looping through the result set and running another DML statement for each result row, use the FORALL statement (see "FORALL Statement").
2.13 FETCH Statement with BULK COLLECT Clause
The FETCH statement with the BULK COLLECT clause (also called the FETCH BULK COLLECT statement) fetches an entire result set into one or more collection variables. For more information, see "FETCH Statement".
Example 12-22 uses a FETCH BULK COLLECT statement to fetch an entire result set into two collections (nested tables).
Example 12-22 Bulk-Fetching into Two Nested Tables
DECLARE
TYPE NameList IS TABLE OF employees.last_name%TYPE;
TYPE SalList IS TABLE OF employees.salary%TYPE;
CURSOR c1 IS
SELECT last_name, salary FROM employees WHERE salary > 10000
ORDER BY last_name;
names NameList;
sals SalList;
TYPE RecList IS TABLE OF c1%ROWTYPE;
recs RecList;
v_limit PLS_INTEGER := 10;
PROCEDURE print_results IS
BEGIN
-- Check if collections are empty:
IF names IS NULL OR names.COUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE('No results!');
ELSE
DBMS_OUTPUT.PUT_LINE('Result: ');
FOR i IN names.FIRST .. names.LAST
LOOP
DBMS_OUTPUT.PUT_LINE(' Employee ' || names(i) || ': $' || sals(i));
END LOOP;
END IF;
END;
BEGIN
DBMS_OUTPUT.PUT_LINE ('--- Processing all results simultaneously ---');
OPEN c1;
FETCH c1 BULK COLLECT INTO names, sals;
CLOSE c1;
print_results();
DBMS_OUTPUT.PUT_LINE ('--- Processing ' || v_limit || ' rows at a time ---');
OPEN c1;
LOOP
FETCH c1 BULK COLLECT INTO names, sals LIMIT v_limit;
EXIT WHEN names.COUNT = 0;
print_results();
END LOOP;
CLOSE c1;
DBMS_OUTPUT.PUT_LINE ('--- Fetching records rather than columns ---');
OPEN c1;
FETCH c1 BULK COLLECT INTO recs;
FOR i IN recs.FIRST .. recs.LAST
LOOP
-- Now all columns from result set come from one record
DBMS_OUTPUT.PUT_LINE (
' Employee ' || recs(i).last_name || ': $' || recs(i).salary
);
END LOOP;
END;
/
Example 12-23 uses a FETCH BULK COLLECT statement to fetch a result set into a collection (nested table) of records.
Example 12-23 Bulk-Fetching into Nested Table of Records
DECLARE
CURSOR c1 IS
SELECT first_name, last_name, hire_date FROM employees;
TYPE NameSet IS TABLE OF c1%ROWTYPE;
stock_managers NameSet; -- nested table of records
TYPE cursor_var_type is REF CURSOR;
cv cursor_var_type;
BEGIN
-- Assign values to nested table of records:
OPEN cv FOR
SELECT first_name, last_name, hire_date FROM employees
WHERE job_id = 'ST_MAN' ORDER BY hire_date;
FETCH cv BULK COLLECT INTO stock_managers;
CLOSE cv;
-- Print nested table of records:
FOR i IN stock_managers.FIRST .. stock_managers.LAST LOOP
DBMS_OUTPUT.PUT_LINE (
stock_managers(i).hire_date || ' ' ||
stock_managers(i).last_name || ', ' ||
stock_managers(i).first_name
);
END LOOP;END;
/
2.14 Row Limits for FETCH BULK COLLECT Statements
A FETCH BULK COLLECT statement that returns a large number of rows produces a large collection. To limit the number of rows and the collection size, use the LIMIT clause.
In Example 12-24, with each iteration of the LOOP statement, the FETCH statement fetches ten rows (or fewer) into associative array empids (overwriting the previous values). Note the exit condition for the LOOP statement.
Example 12-24 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;
EXIT WHEN empids.COUNT = 0; -- Not: EXIT WHEN c1%NOTFOUND
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;
END LOOP;
CLOSE c1;
END;
/
2.15 RETURNING INTO Clause with BULK COLLECT Clause
The RETURNING INTO clause with the BULK COLLECT clause (also called the RETURNING BULK COLLECT INTO clause) can appear in an INSERT, UPDATE, DELETE, or EXECUTE IMMEDIATE statement. With the RETURNING BULK COLLECT INTO clause, the statement stores its result set in one or more collections. For more information, see "RETURNING INTO Clause".
Example 12-25 uses a DELETE statement with the RETURNING BULK COLLECT INTO clause to delete rows from a table and return them in two collections (nested tables).
Example 12-25 Returning Deleted Rows in Two Nested Tables
DROP TABLE emp_temp;
CREATE TABLE emp_temp AS
SELECT * FROM employees
ORDER BY employee_id;
DECLARE
TYPE NumList IS TABLE OF employees.employee_id%TYPE;
enums NumList;
TYPE NameList IS TABLE OF employees.last_name%TYPE;
names NameList;
BEGIN
DELETE FROM emp_temp WHERE department_id = 30
RETURNING employee_id, last_name BULK COLLECT INTO enums, names;
DBMS_OUTPUT.PUT_LINE ('Deleted ' || SQL%ROWCOUNT || ' rows:');
FOR i IN enums.FIRST .. enums.LAST
LOOP
DBMS_OUTPUT.PUT_LINE ('Employee #' || enums(i) || ': ' || names(i));
END LOOP;
END;
/
2.16 Using FORALL Statement and BULK COLLECT Clause Together
In a FORALL statement, the DML statement can have a RETURNING BULK COLLECT INTO clause. For each iteration of the FORALL statement, the DML statement stores the specified values in the specified collections—without overwriting the previous values, as the same DML statement would do in a FOR LOOP statement.
In Example 12-26, the FORALL statement runs a DELETE statement that has a RETURNING BULK COLLECT INTO clause. For each iteration of the FORALL statement, the DELETE statement stores the employee_id and department_id values of the deleted row in the collections e_ids and d_ids, respectively.
Example 12-26 DELETE with RETURN BULK COLLECT INTO in FORALL Statement
DROP TABLE emp_temp;
CREATE TABLE emp_temp AS
SELECT * FROM employees
ORDER BY employee_id, department_id;
DECLARE
TYPE NumList IS TABLE OF NUMBER;
depts NumList := NumList(10,20,30);
TYPE enum_t IS TABLE OF employees.employee_id%TYPE;
e_ids enum_t;
TYPE dept_t IS TABLE OF employees.department_id%TYPE;
d_ids dept_t;
BEGIN
FORALL j IN depts.FIRST..depts.LAST
DELETE FROM emp_temp WHERE department_id = depts(j)
RETURNING employee_id, department_id
BULK COLLECT INTO e_ids, d_ids;
DBMS_OUTPUT.PUT_LINE ('Deleted ' || SQL%ROWCOUNT || ' rows:');
FOR i IN e_ids.FIRST .. e_ids.LAST
LOOP
DBMS_OUTPUT.PUT_LINE (
'Employee #' || e_ids(i) || ' from dept #' || d_ids(i)
);
END LOOP;
END;
/
Example 12-27 is like Example 12-26 except that it uses a FOR LOOP statement instead of a FORALL statement.
Example 12-27 DELETE with RETURN BULK COLLECT INTO in FOR LOOP Statement
DECLARE
TYPE NumList IS TABLE OF NUMBER;
depts NumList := NumList(10,20,30);
TYPE enum_t IS TABLE OF employees.employee_id%TYPE;
e_ids enum_t;
TYPE dept_t IS TABLE OF employees.department_id%TYPE;
d_ids dept_t;
BEGIN
FOR j IN depts.FIRST..depts.LAST LOOP
DELETE FROM emp_temp WHERE department_id = depts(j)
RETURNING employee_id, department_id
BULK COLLECT INTO e_ids, d_ids;
END LOOP;
DBMS_OUTPUT.PUT_LINE ('Deleted ' || SQL%ROWCOUNT || ' rows:');
FOR i IN e_ids.FIRST .. e_ids.LAST
LOOP
DBMS_OUTPUT.PUT_LINE (
'Employee #' || e_ids(i) || ' from dept #' || d_ids(i)
);
END LOOP;
END;
/
2.17 Client Bulk-Binding of Host Arrays
Client programs (such as OCI and Pro*C programs) can use PL/SQL anonymous blocks to bulk-bind input and output host arrays. This is the most efficient way to pass collections to and from the database server.
In the client program, declare and assign values to the host variables to be referenced in the anonymous block. In the anonymous block, prefix each host variable name with a colon (:) to distinguish it from a PL/SQL collection variable name. When the client program runs, the database server runs the PL/SQL anonymous block.
In Example 12-28, the anonymous block uses a FORALL statement to bulk-bind a host input array. In the FORALL statement, the DELETE statement refers to four host variables: scalars lower, upper, and emp_id and array depts.
Example 12-28 Anonymous Block Bulk-Binds Input Host Array
BEGIN
FORALL i IN :lower..:upper
DELETE FROM employees WHERE department_id = :depts(i);
END;
/
三. 小結
在第一節講了Bulk的原理,第二節舉了Bulk的例子。 在這裡做一個簡單的回顧,到底什麼是bulk.
One method of fetching data is an Oracle bulk collect. With Oracle bulk collect, the PL/SQL engine tells the SQL engine to collect many rows at once and place them in a collection. During an Oracle bulk collect, the SQL engine retrieves all the rows and loads them into the collection and switches back to the PL/SQL engine. When rows are retrieved using Oracle bulk collect, they are retrieved with only 2 context switches. The larger the number of rows you would like to collect with Oracle bulk collect, the more performance improvement you will see using an Oracle bulk collect.
Starting in Oracle10g, an Oracle bulk collect may be performed by the the PL/SQL engine for you. The PL/SQL engine may automatically use Oracle bulk collect to collect 100 rows at a time because of a cursor loop. This use of Oracle bulk collect allows your code to process rows without having to setup and execute the Oracle bulk collect operation. The result of this use of Oracle bulk collect is that bulk collecting 75 rows may not provide you with much of a benefit, but using Oracle bulk collect to collect large numbers of rows (many hundreds) will provid increased performance.
Oracle 有2個引擎來執行PL/SQL blocks 和 subprograms。那麼在執行的時候,PL/SQL 引擎把DML 語句傳送給SQL 引擎,然後由SQL 引擎執行,執行完畢後,SQL 引擎把結果集在傳送給PL/SQL 引擎。
這個是一條語句的執行過程,如果我們有一個大事務,比如insert 100萬的資料,那麼這個時候,如果按照原始的方法,每次處理一條,這樣在2個引擎之間就會發生大量的context switches,這樣就會影響SQL的效率。
而bulk 就是從減少引擎之間context switches的方式來提高sql的效率。 把對SQL 進行打包處理。 有2個bulk:
(1)FORALL. 將資料打包,一次性從PL/SQL 引擎傳送給SQL 引擎。
如:
BEGIN
FORALL j IN 1..10
DELETE FROM employees_temp WHERE department_id = depts(j);
END;
如果這裡用for ..loop 迴圈,那麼會傳送10次,而用Forall,一次行全部傳送過去。
(2)bulk collect: 將處理之後的結果集放到bulk collect裡,然後一次性把bulk collect從SQL 引擎傳送給PL/SQL 引擎。 這個bulk collect需要我們先定義好才能使用。
通過以上說明可以看出,如果使用bulk,那麼只有2次context switches,當要處理的資料量越大,使用bulk 和不使用bulk 效能區別就越明顯。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/15880878/viewspace-722962/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Oracle PL/SQLOracleSQL
- Oracle 高效能SQL引擎剖析--SQL優化與調優機制詳解OracleSQL優化
- 【ORACLE】Oracle常用SQL及重點功能說明OracleSQL
- Oracle PL/SQL塊簡介OracleSQL
- 【SQL】關於Oracle12c SQL調整中一些變化SQLOracle
- Oracle SQL調優系列之SQL Monitor ReportOracleSQL
- Oracle 效能調優工具:SQL MonitorOracleSQL
- 如何調優 Oracle SQL系列文章:查詢優化器介紹OracleSQL優化
- Oracle 的PL/SQL語言使用OracleSQL
- Oracle效能優化-SQL優化(案例一)Oracle優化SQL
- Oracle效能優化-SQL優化(案例二)Oracle優化SQL
- Oracle效能優化-SQL優化(案例三)Oracle優化SQL
- Oracle效能優化-SQL優化(案例四)Oracle優化SQL
- Oracle SQL調優之分割槽表OracleSQL
- Oracle SQL優化之sql tuning advisorOracleSQL優化
- Nginx的優化調整方面Nginx優化
- 使用profiler測試Oracle PL/SQL效能OracleSQL
- swoole優化核心引數調整優化
- 備份的優化和調整優化
- Oracle PL/SQL程式碼中的註釋OracleSQL
- 效能調優——SQL最佳化SQL
- Oracle Latch 說明Oracle
- Oracle 調優確定存在問題的SQLOracleSQL
- PL/SQL Developer連線到Oracle 12cSQLDeveloperOracle
- 【OracleEBS】 在PL/SQL中呼叫Oracle ERP請求OracleSQL
- 「Oracle」客戶端 PL/SQL DEVELOPER 安裝使用Oracle客戶端SQLDeveloper
- oracle orapwd使用說明Oracle
- 【ROWID】Oracle rowid說明Oracle
- Oracle vs PostgreSQL Develop(23) - PL(pg)sql(引數宣告)OracleSQLdev
- 原創:oracle PL/SQL程式設計基礎 上OracleSQL程式設計
- 原創:oracle PL/SQL程式設計基礎 下OracleSQL程式設計
- PL/SQL Developer連線遠端Oracle資料庫SQLDeveloperOracle資料庫
- PL/SQL 宣告SQL
- Oracle某行系統SQL優化案例(三)OracleSQL優化
- Oracle某行系統SQL優化(案例五)OracleSQL優化
- Oracle某行系統SQL優化案例(二)OracleSQL優化
- Oracle 某行系統SQL優化案例(一)OracleSQL優化
- 介紹tomcat Connector 引數優化說明Tomcat優化
- oracle 線上調整redoOracle