Tuesday, 24 November 2015

Performance Booster BULK COLLECT /BULK BIND/ CURRENT OF

While  doing programming   in PL/SQL  following are the common scenario of data manipulation


1. selecting multiple  records from the cursor or table (BULK COLLECT)
2. Inserting multiple record in the database(BULK  BIND )
3. Updating the multiple records to the cursor latest row fetched  from the cursor  (CURRENT OF )

 1. selecting multiple  records from the cursor or table (BULK COLLECT)
 
 Concept of Context Switching
=======================
All most every PL/SQL developer writes the SQL and  PL/SQL statement in code.  The  SQL statement are  executed by the SQL engine and PL/SQL statement are executed  by the PL/SQL engine. When PL/SQL engine encounter the SQL statement then it pass the control to the  SQL engine and  again control come backs to PL/SQL engine when PL/SQL  statement encounters.
This is called as context switching.

Following is the procedure  which   will accept the departmentId and salary percentage increase and gives to each employee in the provided department

The following procedure use the cursor (FOR LOOP cursor ) to fetch the employee for the provided departmentId and then update the employee table with increased salary


increase_salary procedure with FOR loop

PROCEDURE increase_salary (
   department_id_in   IN employees.department_id%TYPE,
   increase_pct_in    IN NUMBER)
IS
BEGIN
   FOR employee_rec
      IN (SELECT employee_id
            FROM employees
           WHERE department_id =
                    increase_salary.department_id_in)
   LOOP
      UPDATE employees emp
         SET emp.salary = emp.salary + 
             emp.salary * increase_salary.increase_pct_in
       WHERE emp.employee_id = employee_rec.employee_id;
   END LOOP;
END increase_salary;


Suppose there are 100 employees in department 15. When I execute this block,

BEGIN
   increase_salary (15, .10);
END;
 

When we are executing the above procedure there will be 100 context switching between SQL and PL/SQL engine this is row by row switching  which is performance overhead.




Simplified increase_salary procedure without FOR loop

PROCEDURE increase_salary (
   department_id_in   IN employees.department_id%TYPE,
   increase_pct_in    IN NUMBER)
IS
BEGIN
   UPDATE employees emp
      SET emp.salary =
               emp.salary
             + emp.salary * increase_salary.increase_pct_in
    WHERE emp.department_id = 
             increase_salary.department_id_in;
END increase_salary;



Single context switch to execute update statement  all work is done in single  context switch. By default the update statement is BULK update.


In Real time the code is not that much simple need to perform sever data manipulation operation then updating the data .Suppose that, for example, in the case of the increase_salary procedure, I need to check employees for eligibility for the increase in salary and if they are ineligible, send an e-mail notification. My procedure might then look like below.


PROCEDURE increase_salary (
   department_id_in   IN employees.department_id%TYPE,
   increase_pct_in    IN NUMBER)
IS
   l_eligible   BOOLEAN;
BEGIN
   FOR employee_rec
      IN (SELECT employee_id
            FROM employees
           WHERE department_id =
                    increase_salary.department_id_in)
   LOOP
      check_eligibility (employee_rec.employee_id,
                         increase_pct_in,
                         l_eligible);

      IF l_eligible
      THEN
         UPDATE employees emp
            SET emp.salary =
                     emp.salary
                   +   emp.salary
                     * increase_salary.increase_pct_in
          WHERE emp.employee_id = employee_rec.employee_id;
      END IF;
   END LOOP;
END increase_salary;

Now no longer  everything in single context switch.


Bulk Processing in PL/SQL

The bulk processing features of PL/SQL are designed specifically to reduce the number of context switches required to communicate from the PL/SQL engine to the SQL engine.

Use the BULK COLLECT clause to fetch multiple rows into one or more collections with a single context switch.

Use the FORALL statement when you need to execute the same DML statement repeatedly for different bind variable values. The UPDATE statement in the increase_salary procedure fits this scenario; the only thing that changes with each new execution of the statement is the employee ID.



 CREATE OR REPLACE PROCEDURE increase_salary (
 2     department_id_in   IN employees.department_id%TYPE,
 3     increase_pct_in    IN NUMBER)
 4  IS
 5     TYPE employee_ids_t IS TABLE OF employees.employee_id%TYPE
 6             INDEX BY PLS_INTEGER;
 7     l_employee_ids   employee_ids_t;
 8     l_eligible_ids   employee_ids_t;
 9
10     l_eligible       BOOLEAN;
11  BEGIN
12     SELECT employee_id
13       BULK COLLECT INTO l_employee_ids
14       FROM employees
15      WHERE department_id = increase_salary.department_id_in;
16
17     FOR indx IN 1 .. l_employee_ids.COUNT
18     LOOP

19        check_eligibility (l_employee_ids (indx),
20                           increase_pct_in,
21                           l_eligible);
22
23        IF l_eligible
24        THEN
25           l_eligible_ids (l_eligible_ids.COUNT + 1) :=
26              l_employee_ids (indx);
27        END IF;
28     END LOOP;
29
30     FORALL indx IN 1 .. l_eligible_ids.COUNT
31        UPDATE employees emp
32           SET emp.salary =
33                    emp.salary
34                  + emp.salary * increase_salary.increase_pct_in
35         WHERE emp.employee_id = l_eligible_ids (indx);
36  END increase_salary;



The highlighted green part is the BULK select which will fetch all employee id  in  l_employee_ids 
collection .

The code snippet highlighted in yellow FORALL BULK update
 Rather than move back and forth between PL/SQL and SQL Engine  the FORALL handles all the updates and passes them to the sql engine in single context switch.


IMPORTANT THINK to know when starting to use the advantage of the  BULK COLLECT

Trade of the BULK COLLECT run faster consume more memory .

 There are two types of memory SGA (System Global Area) and PGA(Program Global Area)
the SGA is shared for all the session  connected to the database.  whie PGA is alloacted for each session .  Memory for collection is stored in the PGA.

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.


The PL/SQL made the life of developer easy  by by introducing the  LIMIT clause on the   BULK COLLECT to control amount of memory used.

  FETCH employees_cur 
            BULK COLLECT INTO l_employees LIMIT limit_in;


 Due to LIMIT it will fetch the number_record specified in the limit_in parameter at a time .PL/sQL resuse the same limit_in and same memory for  subsequent fetch. Even though the table size grows the PGA consumption will be constant.

When you are using BULK COLLECT and collections to fetch data from your cursor, you should never rely on the cursor attributes to decide whether to terminate your loop and data processing.
EXIT WHEN 
l_table_with_227_rows.COUNT = 0; 




Generally, you should keep all of the following in mind when working with BULK COLLECT: 

  1.  The collection is always filled sequentially, starting from index value 1.
  2.  It is always safe (that is, you will never raise a NO_DATA_FOUND exception) to iterate through a collection from 1 to collection .COUNT when it has been filled with BULK COLLECT.
  3.  The collection is empty when no rows are fetched.
  4.  Always check the contents of the collection (with the COUNT method) to see if there are more rows to process.
  5.  Ignore the values returned by the cursor attributes, especially %NOTFOUND.