zoukankan      html  css  js  c++  java
  • Reducing Loop Overhead for DML Statements and Queries with Bulk SQL(10gr2)

    PL/SQL运行SQL的机制是把SQL语句,比如DML,SELECT,发送给SQL 引擎。然后SQL引擎把SQL语句的结果返回给PL/SQL。想象一下,下面这样的PL/SQL块。

    FOR j IN 1..99999999 LOOP
      VAR1[i]=i;
      INSERT INTO TEST VALUES(VAR[i]);
    END LOOP;

    这个PL/SQL块非常简洁明了,它循环9999999次,每一次循环都把 i 赋值到 VAR1[i]中,然后把这个VAR1[ i ] insert到表TEST中。这么简单的SQL却有着非常严重的性能问题。因为他要循环99999999次,每一次循环PL/SQL引擎都像SQL引擎发送一条INSERT 语句,这期间的通讯开销会导致运行很慢。 Oracle对这种现象的解决方案是使用批量SQL,比如,FORALL,BULK COLLECT。

    首先,我们看一下FORALL。 这里我们分为2部分讲,

    普通的FORALL

     这里我们做一个比较,先是用普通的FOR把一万条数据插入一个表A中,记录一下时间。然后把相同的一万条数据插入同一个表A中,但这次用FORALL。我们看一下哪个快。

     1 CREATE OR REPLACE PROCEDURE for_vs_forall
     2 AS
     3   timer1 INTEGER;
     4   timer2 INTEGER;
     5   
     6   TYPE num_tab_type IS TABLE OF NUMBER INDEX BY  binary_integer ;
     7   num_table num_tab_type;
     8 BEGIN
     9   /*初始化 num_table*/
    10   FOR i IN 1..9999 LOOP
    11     num_table(i) := i;
    12   END LOOP;
    13   
    14   /*在FOR循环里向A中insert数据,并在循环开始前和循环结束后记录时间,算出循环一共花的时间*/
    15   
    16   timer1 := DBMS_UTILITY.get_time;
    17   FOR i IN 1..9999 LOOP
    18     num_table(i) := i;
    19     insert into A values(num_table(i));
    20   END LOOP;
    21   timer2 := DBMS_UTILITY.get_time;
    22   DBMS_OUTPUT.PUT_LINE('Execution Time (secs)');
    23   DBMS_OUTPUT.PUT_LINE('FOR loop: ' || TO_CHAR((timer2 - timer1)/100));
    24   
    25   
    26   /*清空A表*/
    27   
    28   execute immediate 'delete from A';
    29   
    30   
    31   /*在FORALL循环里向A中insert数据,并在循环开始前和循环结束后记录时间,算出循环一共花的时间*/
    32   
    33   timer1 := DBMS_UTILITY.get_time;
    34   FORALL i IN 1..9999 
    35     INSERT INTO A VALUES(num_table(i));
    36   timer2 := DBMS_UTILITY.get_time;
    37   DBMS_OUTPUT.PUT_LINE('Execution Time (secs)');
    38   DBMS_OUTPUT.PUT_LINE('FORALL loop: ' || TO_CHAR((timer2 - timer1)/100));
    39   
    40 END;
    41     

    简单讲解一下上面的测试,9-10行是初始化num_table这个集合。这里有个小问题,就是如果我在声明这个集合的时候没有指定指定INDEX BY BINARY_INTEGER,那么这里就会报一个ORA-06531: Reference to uninitialized collection的错误。 这个需要研究一下,但我们先跳过这里。 16-23行是通过FOR循环往A表中insert数据,并且在循环开始和循环结束都记录时间以便算出FOR循环的时间。31-38通过FORALL insert数据。在循环开始前和循环结束后都记录并计算时间。  最后看一下两种循环哪个更快。

    1 SQL> exec for_vs_forall
    2 Execution Time (secs)
    3 FOR loop: 2.95
    4 Execution Time (secs)
    5 FORALL loop: .05
    6 
    7 PL/SQL procedure successfully completed.

     运行一下,结果如上所示,FORALL非常快。

    INDICES OF 和 VALUES OF

    /*首先创建三个表,
    valid_orders 记录了customer name 以及对应的 order数量
    big_orders  记录了order 数量比较大的customer name 以及order数量
    rejected_orders 如果有不符合标准的数据,比如 order 数量为NULL 或者0,则把 customer name 和order数量记录进这个表*/
    CREATE TABLE valid_orders (cust_name VARCHAR2(32), amount NUMBER(10,2));
    CREATE TABLE big_orders AS SELECT * FROM valid_orders WHERE 1 = 0;
    CREATE TABLE rejected_orders AS SELECT * FROM valid_orders WHERE 1 = 0;
    
    
    CREATE OR REPLACE PROCEDURE test_forall
    AS
      /*创建两个集合用来存储一系列的customer name 和 order amout 这两个集合类似于数组,相同下表的
      两个元素就组成了一对儿数据对儿,可以存进 valid_order这个表中。 比如 cust_tab(1), amount_tab(1)*/
      SUBTYPE cust_name IS valid_orders.cust_name%TYPE;
      TYPE cust_typ IS TABLE OF cust_name;
      cust_tab cust_typ;
      
      SUBTYPE order_amount IS valid_orders.amount%TYPE;
      TYPE amount_typ IS TABLE OF order_amount;
      amount_tab amount_typ;
      
      /*创建两个数据结构,
      big_order_tab 这个结构里面每一个元素对应着一对customer name和order amount的组合,
      如果里面存着(8,10)那么就意味着第8,10对儿 customer name,order amount应该存进 big_order表
      regected_order_tab记录了应该存进rejected_order的数据对儿*/ 
      TYPE index_pointer_t IS TABLE OF PLS_INTEGER;
      big_order_tab index_pointer_t := index_pointer_t();
      rejected_order_tab index_pointer_t := index_pointer_t();
      
      /*初始化cust_tab 和 amount_tab 生成一些  customer name 和 order amount的数据对儿*/
      PROCEDURE setup_data 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
      
      /*调用setup_data来初始化,生成一些 customer name 和 order amount*/
      setup_data();
      
      /*把这些数据打印出来*/
      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;
      
      /*如果有一些记录不符合标准,比如order amount=0 或者 null,就把对应的数据对儿删掉。*/
      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;
      
      /*输出一下现在的数据, 因为有些数据被删除了,所以输出之前需要用 
      IF cust_tab.EXISTS(i)确定数据是否存*/
      DBMS_OUTPUT.PUT_LINE('--- 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;
      
      /*注意这里才是重点,因为cust_tab里有一些数据被删除了,这时候如果你还用
      FORALL i IN cust_tab.FIRST .. cust_tab.LAST
      或者
      FORALL i IN 1 .. count
      这两种形式,就会出错。 会碰到ORA-22160: element at index [2] does not exist这种错误,
      所以需要下面的这种执行方式。
      */
      FORALL i IN INDICES OF cust_tab
        INSERT INTO valid_orders(cust_name, amount) VALUES(cust_tab(i), amount_tab(i));
        
    
      /*下面重新初始化数据,学习一下 values of*/
      setup_data();
      
      /*这里遍历一下cust_tab 以及 amount_tab这两个表,如果甄别数据,把对应的数据放入big_order 
      或者rejected_order*/
      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;
      
      
      /*这里要注意一个前提就是 big_order_tab中存储的内容是指向cust_tab和order_tab的指针,所以
      如果你还像以前这样写
      FORALL i IN  big_order_tab.FIRST .. big_order_tab.LAST
        INSERT INTO big_orders VALUES (cust_tab(i), amount_tab(i));
      那么你插入的数据就错了。因为这时候的i是 big_order_tab的下标而不是内容,你需要这样写 
      */
      FORALL i IN VALUES OF rejected_order_tab
        INSERT INTO rejected_orders VALUES (cust_tab(i), amount_tab(i));
      FORALL i IN VALUES OF big_order_tab
        INSERT INTO big_orders VALUES (cust_tab(i), amount_tab(i));
      COMMIT;
      
    END;
    

     运行一下

    SQL> delete from valid_orders;
    
    0 rows deleted.
    
    SQL> delete from big_orders;
    
    4 rows deleted.
    
    SQL> delete from rejected_orders;
    
    0 rows deleted.
    
    SQL> commit;
    
    Commit complete.
    
    
    SQL> EXEC test_forall
    --- Original order data ---
    Customer #1, Company1: $5000.01
    Customer #2, Company2: $0
    Customer #3, Company3: $150.25
    Customer #4, Company4: $4000
    Customer #5, Company5: $
    --- Data with invalid orders deleted ---
    Customer #1, Company1: $5000.01
    Customer #3, Company3: $150.25
    Customer #4, Company4: $4000
    
    PL/SQL procedure successfully completed.
    
    SQL> select * from valid_orders;
    
    CUST_NAME                                                                                            AMOUNT
    ------------------------------------------------------------------------------------------------ ----------
    Company1                                                                                            5000.01
    Company3                                                                                             150.25
    Company4                                                                                               4000
    
    SQL> select * from big_orders;
    
    CUST_NAME                                                                                            AMOUNT
    ------------------------------------------------------------------------------------------------ ----------
    Company1                                                                                            5000.01
    Company4                                                                                               4000
    
    SQL> select * from rejected_orders;
    
    CUST_NAME                                                                                            AMOUNT
    ------------------------------------------------------------------------------------------------ ----------
    Company2                                                                                                  0
    Company5
  • 相关阅读:
    第09组 Alpha冲刺(3/4)
    第09组 Alpha冲刺(2/4)
    第09组 Alpha冲刺(1/4)
    数值分析——多项式插值之Lagrange插值
    Deepin下安装搭建latex编写环境
    Deepin下安装Matlab2017a永久版
    数学建模方法-遗传算法(实战篇part 2)
    数学建模方法-遗传算法(实战篇part 1)
    数学建模方法-遗传算法(理论篇)
    Deepin系统下MATLAB中文字体乱码问题解决(加上字体美化)
  • 原文地址:https://www.cnblogs.com/kramer/p/3042063.html
Copyright © 2011-2022 走看看