Skip to main content

All About Bulk Binding

Oracle Database uses two engines to run PL/SQL blocks and subprograms. The PL/SQL engine runs procedural statements, while the SQL engine runs SQL statements. During execution, every SQL statement causes a context switch between the two engines, resulting in performance overhead.

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 the following:

  • Varrays
  • Nested tables
  • Index-by tables
  • Host arrays

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.

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.

How to Bulk Bind

Bulk binding includes the following:

1.    Input collections, use the FORALL statement
2.    Output collections, use BULK COLLECT clause

When to Use Bulk Binds with Examples.

If you have scenarios like these in your applications, consider using bulk binds to improve performance.

DML Statements that Reference Collections

The FORALL keyword can improve the performance of INSERT, UPDATE, or DELETE statements that reference collection elements.
For example, the following PL/SQL block increases the salary for employees whose manager's ID number is 7902, 7698, or 7839, both with and without using bulk binds:

DECLARE
   TYPE Numlist IS VARRAY (100) OF NUMBER;
   Id NUMLIST := NUMLIST(7902, 7698, 7839);
BEGIN

-- Efficient method, using a bulk bind
   FORALL i IN Id.FIRST..Id.LAST   -- bulk-bind the VARRAY
      UPDATE Emp_tab SET Sal = 1.1 * Sal
      WHERE Mgr = Id(i);

-- Slower method, running the UPDATE statements within a regular loop
   FOR i IN Id.FIRST..Id.LAST LOOP
      UPDATE Emp_tab SET Sal = 1.1 * Sal
      WHERE Mgr = Id(i);
   END LOOP;
END;


Without the bulk bind, PL/SQL sends a SQL statement to the SQL engine for each employee that is updated, leading to context switches that hurt performance.

If you have a set of rows prepared in a PL/SQL table, you can bulk-insert or bulk-update the data using a loop like:

FORALL i in Emp_Data.FIRST..Emp_Data.LAST
    INSERT INTO Emp_tab VALUES(Emp_Data(i));


SELECT Statements that Reference Collections

The BULK COLLECT INTO clause can improve the performance of queries that reference collections.
For example, the following PL/SQL block queries multiple values into PL/SQL tables, both with and without bulk binds:

-- Find all employees whose manager's ID number is 7698.
DECLARE
   TYPE Var_tab IS TABLE OF VARCHAR2(20) INDEX BY BINARY_INTEGER;
   Empno VAR_TAB;
   Ename VAR_TAB;
   Counter NUMBER;
   CURSOR C IS
      SELECT Empno, Ename FROM Emp_tab WHERE Mgr = 7698;
BEGIN

-- Efficient method, using a bulk bind
    SELECT Empno, Ename BULK COLLECT INTO Empno, Ename
        FROM Emp_Tab WHERE Mgr = 7698;

-- Slower method, assigning each collection element within a loop.

   counter := 1;
   FOR rec IN C LOOP
      Empno(Counter) := rec.Empno;
      Ename(Counter) := rec.Ename;
      Counter := Counter + 1;
   END LOOP;
END;


You can use BULK COLLECT INTO with tables of scalar values, or tables of %TYPE values.Without the bulk bind, PL/SQL sends a SQL statement to the SQL engine for each employee that is selected, leading to context switches that hurt performance.

FOR Loops that Reference Collections and the Returning Into Clause

You can use the FORALL keyword along with the BULK COLLECT INTO keywords to improve the performance of FOR loops that reference collections and return DML.

For example, the following PL/SQL block updates the Emp_tab table by computing bonuses for a collection of employees; then it returns the bonuses in a column called Bonlist. The actions are performed both with and without using bulk binds:

DECLARE
   TYPE Emplist IS VARRAY(100) OF NUMBER;
   Empids EMPLIST := EMPLIST(7369, 7499, 7521, 7566, 7654, 7698);
   TYPE Bonlist IS TABLE OF Emp_tab.sal%TYPE;
   Bonlist_inst BONLIST;
BEGIN
   Bonlist_inst := BONLIST(1,2,3,4,5);

   FORALL i IN Empids.FIRST..empIDs.LAST
      UPDATE Emp_tab SET Bonus = 0.1 * Sal
      WHERE Empno = Empids(i)
      RETURNING Sal BULK COLLECT INTO Bonlist;

   FOR i IN Empids.FIRST..Empids.LAST LOOP
      UPDATE Emp_tab Set Bonus = 0.1 * sal      
         WHERE Empno = Empids(i)
       RETURNING Sal INTO BONLIST(i);
   END LOOP;
END;


Without the bulk bind, PL/SQL sends a SQL statement to the SQL engine for each employee that is updated, leading to context switches that hurt performance.

Control Memory Used in a BULK COLLECT with LIMIT Keyword


The most important thing to remember when you learn about and start to take advantage of features such as BULK COLLECT is that there is no free lunch. There is almost always a trade-off to be made somewhere. The tradeoff with BULK COLLECT, like so many other performance-enhancing features, is "run faster but consume more memory."

Specifically, memory for collections is stored in the program global area (PGA), not the system global area (SGA). SGA memory is shared by all sessions connected to Oracle Database, but PGA memory is allocated for each session. Thus, if a program requires 5MB of memory to populate a collection and there are 100 simultaneous connections, that program causes the consumption of 500MB of PGA memory, in addition to the memory allocated to the SGA.

Fortunately, PL/SQL makes it easy for developers to control the amount of memory used in a BULK COLLECT operation by using the LIMIT clause.

Suppose I need to retrieve all the rows from the employees table and then perform some compensation analysis on each row. I can use BULK COLLECT as follows:

PROCEDURE process_all_rows
IS
   TYPE employees_aat IS TABLE OF employees%ROWTYPE INDEX BY PLS_INTEGER;
   l_employees employees_aat;
BEGIN
  SELECT *
   BULK COLLECT INTO l_employees
      FROM employees;   
   FOR indx IN 1 .. l_employees.COUNT
     LOOP
        analyze_compensation (l_employees(indx));
   END LOOP;
END process_all_rows;


Very concise, elegant, and efficient code. If, however, my employees table contains tens of thousands of rows, each of which contains hundreds of columns, this program can cause excessive PGA memory consumption.

Consequently, you should avoid this sort of "unlimited" use of BULK COLLECT. Instead, move the SELECT statement into an explicit cursor declaration and then use a simple loop to fetch many, but not all, rows from the table with each execution of the loop body, as shown below.
Using BULK COLLECT with LIMIT clause

PROCEDURE process_all_rows (limit_in IN PLS_INTEGER DEFAULT 100)
IS
    CURSOR employees_cur
    IS
        SELECT * FROM employees;
    TYPE employees_aat IS TABLE OF employees_cur%ROWTYPE INDEX BY PLS_INTEGER;
    l_employees employees_aat;
BEGIN
    OPEN employees_cur;
    LOOP
        FETCH employees_cur
            BULK COLLECT INTO l_employees LIMIT limit_in;
        FOR indx IN 1 .. l_employees.COUNT
        LOOP
            analyze_compensation (l_employees(indx));
        END LOOP;
        EXIT WHEN l_employees.COUNT < limit_in;
   END LOOP;
   CLOSE employees_cur;
END process_all_rows;


The process_all_rows procedure above requests that up to the value of limit_in rows be fetched at a time. PL/SQL will reuse the same limit_in elements in the collection each time the data is fetched and thus also reuse the same memory. Even if my table grows in size, the PGA consumption will remain stable.

Restriction removed while using Bulk Binding

The PLS-00436 restriction has been removed from 11g, and now we can reference each element of the collection object when used with FORALL statement. Each element can now be referenced using SET and WHERE clauses of a DML statement in a FORALL construct.

Comments

Popular posts from this blog

Check Whether File Exists on the Server Using PLSQL

There are different packages using which you can check whether a file exists on the database / application server. The different ways are 1. Using UTL_HTTP from Web Server 2. Using UTL_FILE from Database Server 3. Using DBMS_LOB from Database Server 1. You can also use UTL_HTTP package to check whether a file exists on a web server from the database. SET SERVEROUTPUT ON DECLARE   url       VARCHAR2(256) := 'http://www.oracle.com/index1.html';   username  VARCHAR2(256);   password  VARCHAR2(256);   req       UTL_HTTP.REQ;   resp      UTL_HTTP.RESP; BEGIN   req := UTL_HTTP.BEGIN_REQUEST(url);   IF (username IS NOT NULL) THEN     UTL_HTTP.SET_AUTHENTICATION(req, username, password);   END IF;   resp := UTL_HTTP.GET_RESPONSE(req);   DBMS_OUTPUT.PUT_LINE('response -->' || resp.status_code); END; / If the f...

11.2 PLSQL New Feature : How to use DBMS_PARALLEL_EXECUTE to Update Large Tables in Parallel

The DBMS_PARALLEL_EXECUTE package enables you to incrementally update the data in a large table in parallel, in two high-level steps: 1. Group sets of rows in the table into smaller chunks. 2. Apply the desired UPDATE statement to the chunks in parallel, committing each time you have finished processing a chunk. This technique is recommended whenever you are updating a lot of data. Its advantages are: o You lock only one set of rows at a time, for a relatively short time, instead of locking the entire table. o You do not lose work that has been done if something fails before the entire operation finishes. o You reduce rollback space consumption. o You improve performance Different Ways to Spilt Workload : 1. CREATE_CHUNKS_BY_NUMBER_COL: Chunks the table associated with the given task by the specified column. 2. CREATE_CHUNKS_BY_ROWID : Chunks the table associated with the given task by ROWID 3. CREATE_CHUNKS_BY_SQL : Chunks the table associated with the given task by me...

PRAGMA AUTONOMOUS_TRANSACTION with Transaction Isolation

What is PRAGMA ? PRAGMA is a language construct that specifies how a compiler (or assembler or interpreter) should process its input. PRAGMA keyword in PLSQL is used to direct compiler to work differently when compared to the normal code. Pragmas are processed at compile time, not at run time. What is AUTONOMOUS_TRANSACTION ? The AUTONOMOUS_TRANSACTION pragma changes the way a subprogram works within a transaction. A subprogram marked with this pragma can do SQL operations and commit or roll back those operations, without committing or rolling back the data in the main transaction. The statement clearly states that autonomous transaction executes independent of main transaction. The concept was crystal clear to me and I had used it extensively in designing some of the solution. Very recently a question raised in my mind. Will the changes made by autonomous transaction be visible in the main transaction ?  I didn't have an answer and hence tried to do a research. L...