plsql優化笛卡爾積

轉載自:http://www.itpub.net/thread-1840767-1-1.html

寫在最前,是對我自己而言收穫最大的想法。
oracle的優化,瞭解CBO很重要,分析執行計劃很重要;
但是,優化絕不止於CBO,相比之下優化必談CBO,我覺得多少有點誤區。
我們究竟是否明白一個sql或者一段plsql執行過程中哪步最費時?爲什麼?
--******************************************************
正題
論壇上有很多網友問過這樣的問題:
我的表中有一個字段text,有一個分隔符 ',',需要按照分隔符把數據分爲n行,每行按順序取夾在分隔符中的部分
比如數據是
id                        text
1                        a, b, c, d
2                        e, f, g
我需要的結果是
1        a
1        b
1        c
1        d
2        e
2        f
2        g

這時候,很多網友會用itpub上最經典的sql回覆
  1. select        id, regexp_substr(text, '[^'||chr(10)||']+', 1, level) item_txt
  2. from        t1
  3. connect by prior id=id and level<=length(text)-length(replace(text, chr(10))) and prior dbms_random.value>0;
複製代碼
當然,也可能有“正則黑”,會用substr套instr的版本來實現。
當然,也會有笛卡爾積的版本。
總之,這已經成了一個套路。

但是,這樣做真的好麼?
我以前從來沒思考過這個問題,直到我的項目中真正接觸到了這個問題,我才發現,sql的幾個寫法,幾乎只有理論上的意義。
我們來測測。
  1. create table t1 (id int, text varchar2(4000));
  2. insert into t1
  3. select        1, listagg(lpad('a', 60), chr(10)) within group (order by 1)||chr(10)
  4. from        dual
  5. connect by rownum<=64;

  6. insert into t1
  7. select        n+1, text
  8. from        t1, (select rownum n from dual connect by rownum<=199);
複製代碼
  1. --sql connect by 寫法
  2. bill@ORCL> select       id, regexp_substr(text, '[^'||chr(10)||']+', 1, level) item_txt
  3.   2  from       t1
  4.   3  connect by prior id=id and level<=length(text)-length(replace(text, chr(10))) and prior dbms_random.value>0;

  5. 已選擇 12800 行。

  6. 已用時間:  00: 00: 16.24

  7. 執行計劃
  8. ----------------------------------------------------------
  9. Plan hash value: 3874795171

  10. -------------------------------------------------------------------------------------
  11. | Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
  12. -------------------------------------------------------------------------------------
  13. |   0 | SELECT STATEMENT             |      |   186 |   366K|    68   (0)| 00:00:01 |
  14. |*  1 |  CONNECT BY WITHOUT FILTERING|      |       |       |            |          |
  15. |   2 |   TABLE ACCESS FULL          | T1   |   186 |   366K|    68   (0)| 00:00:01 |
  16. -------------------------------------------------------------------------------------

  17. Predicate Information (identified by operation id):
  18. ---------------------------------------------------

  19.    1 - access("ID"=PRIOR "ID")
  20.        filter(LEVEL<=LENGTH("TEXT")-LENGTH(REPLACE("TEXT",' ')) AND PRIOR
  21.               "DBMS_RANDOM"."VALUE"()>0)

  22. Note
  23. -----
  24.    - dynamic statistics used: dynamic sampling (level=2)


  25. 統計信息
  26. ----------------------------------------------------------
  27.           4  recursive calls
  28.           0  db block gets
  29.         313  consistent gets
  30.           0  physical reads
  31.           0  redo size
  32.      228399  bytes sent via SQL*Net to client
  33.        9927  bytes received via SQL*Net from client
  34.         855  SQL*Net roundtrips to/from client
  35.           1  sorts (memory)
  36.           0  sorts (disk)
  37.       12800  rows processed
複製代碼
16秒這個時間有點出乎我的意料。我沒想到會這麼慢,試試substr套instr的版本
  1. bill@ORCL> select       id, substr(text,
  2.   2                                decode(level, 1, 1, instr(text, chr(10), 1, level-1)+1)),
  3.   3                                instr(text, chr(10), 1, level)-decode(level, 1, 1, instr(text, chr(10), 1, level-1)+1)
  4.   4  from       t1
  5.   5  connect by prior id=id and level<=length(text)-length(replace(text, chr(10))) and prior dbms_random.value>0;

  6. 已選擇 12800 行。

  7. 已用時間:  00: 00: 04.89

  8. 執行計劃
  9. ----------------------------------------------------------
  10. Plan hash value: 3874795171

  11. -------------------------------------------------------------------------------------
  12. | Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
  13. -------------------------------------------------------------------------------------
  14. |   0 | SELECT STATEMENT             |      |   186 |   366K|    68   (0)| 00:00:01 |
  15. |*  1 |  CONNECT BY WITHOUT FILTERING|      |       |       |            |          |
  16. |   2 |   TABLE ACCESS FULL          | T1   |   186 |   366K|    68   (0)| 00:00:01 |
  17. -------------------------------------------------------------------------------------

  18. Predicate Information (identified by operation id):
  19. ---------------------------------------------------

  20.    1 - access("ID"=PRIOR "ID")
  21.        filter(LEVEL<=LENGTH("TEXT")-LENGTH(REPLACE("TEXT",' ')) AND PRIOR
  22.               "DBMS_RANDOM"."VALUE"()>0)

  23. Note
  24. -----
  25.    - dynamic statistics used: dynamic sampling (level=2)


  26. 統計信息
  27. ----------------------------------------------------------
  28.           0  recursive calls
  29.           0  db block gets
  30.         248  consistent gets
  31.           0  physical reads
  32.           0  redo size
  33.    25757310  bytes sent via SQL*Net to client
  34.        9927  bytes received via SQL*Net from client
  35.         855  SQL*Net roundtrips to/from client
  36.           1  sorts (memory)
  37.           0  sorts (disk)
  38.       12800  rows processed
複製代碼
強多了,看來oracle的正則最好是能不用盡量不用。
但是仍然很不滿意,這才幾條數據啊。
試試笛卡爾積的版本

  1. bill@ORCL> select       substr(text,
  2.   2                        decode(lv, 1, 1, instr(text, chr(10), 1, lv-1)+1)),
  3.   3                        instr(text, chr(10), 1, lv)-decode(lv, 1, 1, instr(text, chr(10), 1, lv-1)+1)
  4.   4  from       t1,
  5.   5             (select rownum lv from dual connect by rownum<=64) b;

  6. 已選擇 12800 行。

  7. 已用時間:  00: 00: 03.30

  8. 執行計劃
  9. ----------------------------------------------------------
  10. Plan hash value: 894562235

  11. ----------------------------------------------------------------------------------------
  12. | Id  | Operation                       | Name | Rows  | Bytes | Cost (%CPU)| Time     |
  13. ----------------------------------------------------------------------------------------
  14. |   0 | SELECT STATEMENT                |      |   186 |   366K|    70   (0)| 00:00:01 |
  15. |   1 |  MERGE JOIN CARTESIAN           |      |   186 |   366K|    70   (0)| 00:00:01 |
  16. |   2 |   VIEW                          |      |     1 |    13 |     2   (0)| 00:00:01 |
  17. |   3 |    COUNT                        |      |       |       |            |          |
  18. |*  4 |     CONNECT BY WITHOUT FILTERING|      |       |       |            |          |
  19. |   5 |      FAST DUAL                  |      |     1 |       |     2   (0)| 00:00:01 |
  20. |   6 |   BUFFER SORT                   |      |   186 |   363K|    70   (0)| 00:00:01 |
  21. |   7 |    TABLE ACCESS FULL            | T1   |   186 |   363K|    68   (0)| 00:00:01 |
  22. ----------------------------------------------------------------------------------------

  23. Predicate Information (identified by operation id):
  24. ---------------------------------------------------

  25.    4 - filter(ROWNUM<=64)

  26. Note
  27. -----
  28.    - dynamic statistics used: dynamic sampling (level=2)


  29. 統計信息
  30. ----------------------------------------------------------
  31.          48  recursive calls
  32.           0  db block gets
  33.         435  consistent gets
  34.           2  physical reads
  35.           0  redo size
  36.      359032  bytes sent via SQL*Net to client
  37.        9927  bytes received via SQL*Net from client
  38.         855  SQL*Net roundtrips to/from client
  39.           8  sorts (memory)
  40.           0  sorts (disk)
  41.       12800  rows processed
複製代碼
又快一點,不過僅僅一點而已,數量級是不會變了。
試試物理表
  1. create table t2 (id int, lv int, text varchar2(4000));
  2. insert into t2
  3. select        (n-1)*200+id, n, text
  4. from        t1, (select rownum n from dual connect by rownum<=64);

  5. bill@ORCL> select       substr(text,
  6.   2                        decode(lv, 1, 1, instr(text, chr(10), 1, lv-1)+1)),
  7.   3                        instr(text, chr(10), 1, lv)-decode(lv, 1, 1, instr(text, chr(10), 1, lv-1)+1)
  8.   4  from       t2;

  9. 已選擇 12800 行。

  10. 已用時間:  00: 00: 03.53

  11. 執行計劃
  12. ----------------------------------------------------------
  13. Plan hash value: 1513984157

  14. --------------------------------------------------------------------------
  15. | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
  16. --------------------------------------------------------------------------
  17. |   0 | SELECT STATEMENT  |      | 15663 |    30M|  3567   (1)| 00:00:01 |
  18. |   1 |  TABLE ACCESS FULL| T2   | 15663 |    30M|  3567   (1)| 00:00:01 |
  19. --------------------------------------------------------------------------

  20. Note
  21. -----
  22.    - dynamic statistics used: dynamic sampling (level=2)


  23. 統計信息
  24. ----------------------------------------------------------
  25.           0  recursive calls
  26.           0  db block gets
  27.       13005  consistent gets
  28.       12997  physical reads
  29.           0  redo size
  30.     3526140  bytes sent via SQL*Net to client
  31.        9927  bytes received via SQL*Net from client
  32.         855  SQL*Net roundtrips to/from client
  33.           0  sorts (memory)
  34.           0  sorts (disk)
  35.       12800  rows processed
複製代碼
從實驗現象來看,似乎oracle不管你select的substr需要多少空間,都把全表複製64份,再取substr。如果是這樣,那這裏的內存操作實在太低效了。
多做兩個實驗看看。
  1. bill@ORCL> select       substr(text,
  2.   2                        decode(lv, 1, 1, instr(text, chr(10), 1, lv-1)+1)),
  3.   3                        instr(text, chr(10), 1, lv)-decode(lv, 1, 1, instr(text, chr(10), 1, lv-1)+1)
  4.   4  from       t1,
  5.   5             (
  6.   6             select  id, lv
  7.   7             from    (select id from t1),
  8.   8                             (select rownum lv from dual connect by rownum<=64)
  9.   9             where   rownum>0
  10. 10             ) b
  11. 11  where      t1.id=b.id;

  12. 已選擇 12800 行。

  13. 已用時間:  00: 00: 03.56

  14. 執行計劃
  15. ----------------------------------------------------------
  16. Plan hash value: 553474792

  17. ----------------------------------------------------------------------------------------------------
  18. | Id  | Operation                           | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
  19. ----------------------------------------------------------------------------------------------------
  20. |   0 | SELECT STATEMENT                    |              |   187 |   372K|    71   (0)| 00:00:01 |
  21. |*  1 |  HASH JOIN                          |              |   187 |   372K|    71   (0)| 00:00:01 |
  22. |   2 |   VIEW                              |              |   186 |  4836 |     3   (0)| 00:00:01 |
  23. |   3 |    COUNT                            |              |       |       |            |          |
  24. |*  4 |     FILTER                          |              |       |       |            |          |
  25. |   5 |      MERGE JOIN CARTESIAN           |              |   186 |  4836 |     3   (0)| 00:00:01 |
  26. |   6 |       VIEW                          |              |     1 |    13 |     2   (0)| 00:00:01 |
  27. |   7 |        COUNT                        |              |       |       |            |          |
  28. |*  8 |         CONNECT BY WITHOUT FILTERING|              |       |       |            |          |
  29. |   9 |          FAST DUAL                  |              |     1 |       |     2   (0)| 00:00:01 |
  30. |  10 |       BUFFER SORT                   |              |   186 |  2418 |     3   (0)| 00:00:01 |
  31. |  11 |        INDEX FULL SCAN              | SYS_C0010642 |   186 |  2418 |     1   (0)| 00:00:01 |
  32. |  12 |   TABLE ACCESS FULL                 | T1           |   186 |   366K|    68   (0)| 00:00:01 |
  33. ----------------------------------------------------------------------------------------------------

  34. Predicate Information (identified by operation id):
  35. ---------------------------------------------------

  36.    1 - access("T1"."ID"="B"."ID")
  37.    4 - filter(ROWNUM>0)
  38.    8 - filter(ROWNUM<=64)

  39. Note
  40. -----
  41.    - dynamic statistics used: dynamic sampling (level=2)


  42. 統計信息
  43. ----------------------------------------------------------
  44.          15  recursive calls
  45.           0  db block gets
  46.         515  consistent gets
  47.           9  physical reads
  48.           0  redo size
  49.    25712495  bytes sent via SQL*Net to client
  50.        9927  bytes received via SQL*Net from client
  51.         855  SQL*Net roundtrips to/from client
  52.           2  sorts (memory)
  53.           0  sorts (disk)
  54.       12800  rows processed
複製代碼
可以看到,改寫的速度也是一樣,似乎效率還要更差一點。
再看看我們明確的告訴oracle select的字段內容,它的表現怎麼樣,
  1. bill@ORCL> select       substr(t1.id, instr(text, chr(10), 1, 58), instr(text, chr(10), 1, 59)-instr(text, chr(10), 1, 58)-1) item_txt
  2.   2  from       t1,
  3.   3             (select rownum lv from dual connect by rownum<=64) b;

  4. 已選擇 12800 行。

  5. 已用時間:  00: 00: 01.26

  6. 執行計劃
  7. ----------------------------------------------------------
  8. Plan hash value: 894562235

  9. ----------------------------------------------------------------------------------------
  10. | Id  | Operation                       | Name | Rows  | Bytes | Cost (%CPU)| Time     |
  11. ----------------------------------------------------------------------------------------
  12. |   0 | SELECT STATEMENT                |      |   186 |   366K|    70   (0)| 00:00:01 |
  13. |   1 |  MERGE JOIN CARTESIAN           |      |   186 |   366K|    70   (0)| 00:00:01 |
  14. |   2 |   VIEW                          |      |     1 |       |     2   (0)| 00:00:01 |
  15. |   3 |    COUNT                        |      |       |       |            |          |
  16. |*  4 |     CONNECT BY WITHOUT FILTERING|      |       |       |            |          |
  17. |   5 |      FAST DUAL                  |      |     1 |       |     2   (0)| 00:00:01 |
  18. |   6 |   BUFFER SORT                   |      |   186 |   366K|    70   (0)| 00:00:01 |
  19. |   7 |    TABLE ACCESS FULL            | T1   |   186 |   366K|    68   (0)| 00:00:01 |
  20. ----------------------------------------------------------------------------------------

  21. Predicate Information (identified by operation id):
  22. ---------------------------------------------------

  23.    4 - filter(ROWNUM<=64)

  24. Note
  25. -----
  26.    - dynamic statistics used: dynamic sampling (level=2)


  27. 統計信息
  28. ----------------------------------------------------------
  29.           7  recursive calls
  30.           0  db block gets
  31.         380  consistent gets
  32.           2  physical reads
  33.           0  redo size
  34.      227513  bytes sent via SQL*Net to client
  35.        9927  bytes received via SQL*Net from client
  36.         855  SQL*Net roundtrips to/from client
  37.           2  sorts (memory)
  38.           0  sorts (disk)
  39.       12800  rows processed
複製代碼
又快了一些。
那經過這些測試,似乎可以得出這樣的結論,cpu運算和內存操作現在是需要考慮的重要因素(本來也是,只不過我們似乎總是做不了什麼來優化這些)。
很顯然的結論就是,長的字符串,我們對它進行的substr和instr操作,自然會更慢,變量的賦值,效率也會更低。
思考到這裏,似乎需要轉向plsql了。
因爲我的需求中,打散之後的結果是要和其他表繼續關聯,所以我理所當然想要用管道表函數來試試。
第一個版本的代碼並不難寫,除了plsql中這些複雜的語法,當然複雜是相對sql而言,其他高級編程語言請呵呵。
  1. create or replace package refcur_pkg_v1
  2. authid current_user
  3. as
  4.     type inrec is record (
  5.         id                        number(38),
  6.         text                varchar2(4000));
  7.     type refcur_t is ref cursor return inrec;
  8.     type outrec_typ is record (
  9.         id                        number(38),
  10.         item_txt        varchar2(4000));
  11.     type outrecset is table of outrec_typ;
  12.     function f_cartesian (p refcur_t) return outrecset pipelined
  13.     parallel_enable (partition p by any);
  14. end;
  15. /

  16. create or replace PACKAGE BODY refcur_pkg_v1 IS
  17.     FUNCTION f_cartesian (p refcur_t) RETURN outrecset PIPELINED
  18.     parallel_enable (partition p by any)
  19.     IS
  20.         in_rec      p%ROWTYPE;
  21.         out_rec     outrec_typ;
  22.         C_NL_TERM   varchar2(2) := chr(10); --unix style
  23.         v_nl_pos    int:=0;
  24.         v_tmp_pos        int:=0;
  25.     BEGIN
  26.         LOOP
  27.             FETCH p INTO in_rec;  -- input row
  28.             EXIT WHEN p%NOTFOUND;

  29.                 v_nl_pos        :=0;
  30.                 v_tmp_pos        :=0;
  31.             out_rec.id        :=in_rec.id;
  32.             FOR i IN 1..100000 LOOP
  33.                 v_tmp_pos:=instr(in_rec.text, C_NL_TERM, v_nl_pos+1);
  34.                                 exit when v_tmp_pos=0;
  35.                 out_rec.item_txt        :=substr(in_rec.text, v_nl_pos+1, v_tmp_pos-v_nl_pos-1);
  36.                 v_nl_pos                        :=v_tmp_pos;
  37.                 PIPE ROW(out_rec);
  38.             END LOOP;

  39.         END LOOP;
  40.         CLOSE p;
  41.         RETURN;
  42.     END f_cartesian;
  43. END refcur_pkg_v1;
  44. /
複製代碼
  1. bill@ORCL> select       id, item_txt
  2.   2  from       table(refcur_pkg_v1.f_cartesian(cursor(
  3.   3                     select  id, text
  4.   4                     from    t1
  5.   5                     )));

  6. 已選擇 12800 行。

  7. 已用時間:  00: 00: 00.70

  8. 執行計劃
  9. ----------------------------------------------------------
  10. Plan hash value: 4049074522

  11. --------------------------------------------------------------------------------------------------
  12. | Id  | Operation                          | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
  13. --------------------------------------------------------------------------------------------------
  14. |   0 | SELECT STATEMENT                   |             |  8168 |   271K|    29   (0)| 00:00:01 |
  15. |   1 |  VIEW                              |             |  8168 |   271K|    29   (0)| 00:00:01 |
  16. |   2 |   COLLECTION ITERATOR PICKLER FETCH| F_CARTESIAN |  8168 |       |    29   (0)| 00:00:01 |
  17. |   3 |    TABLE ACCESS FULL               | T1          |   186 |   366K|    68   (0)| 00:00:01 |
  18. --------------------------------------------------------------------------------------------------

  19. Note
  20. -----
  21.    - dynamic statistics used: dynamic sampling (level=2)


  22. 統計信息
  23. ----------------------------------------------------------
  24.         217  recursive calls
  25.           0  db block gets
  26.         264  consistent gets
  27.           0  physical reads
  28.           0  redo size
  29.      228399  bytes sent via SQL*Net to client
  30.        9927  bytes received via SQL*Net from client
  31.         855  SQL*Net roundtrips to/from client
  32.           0  sorts (memory)
  33.           0  sorts (disk)
  34.       12800  rows processed
複製代碼
0.7秒!看來方向是正確的。
但是我的需求中數據量很龐大,最好還能繼續優化一下。
現在的版本是帶着整個text的內容去循環,那很自然的會想用二分法去試一試。
  1. create or replace package refcur_pkg
  2. authid current_user
  3. as
  4.     type inrec is record (
  5.         id                        number(38),
  6.         lines                number(38),
  7.         text                varchar2(4000));
  8.     type refcur_t is ref cursor return inrec;
  9.     type outrec_typ is record (
  10.         id                        number(38),
  11.         item_txt        varchar2(4000));
  12.     type outrecset is table of outrec_typ;
  13.     function f_cartesian (p refcur_t) return outrecset pipelined
  14.     parallel_enable (partition p by any);
  15.     function f_cartesian2 (p refcur_t) return outrecset pipelined
  16.     parallel_enable (partition p by any);
  17.     function f_cartesian3 (p refcur_t) return outrecset pipelined
  18.     parallel_enable (partition p by any);
  19. end;
  20. /

  21. create or replace package body refcur_pkg IS
  22.     function f_cartesian (p refcur_t) return outrecset pipelined
  23.     parallel_enable (partition p by any)
  24.     is
  25.         in_rec      p%ROWTYPE;
  26.         out_rec     outrec_typ;
  27.         C_NL_TERM   varchar2(2) := chr(10); --unix style
  28.         C_NL_LENG   int := 1;                           --unix new line length
  29.         C_SIZE                int:=64;                                --split clob to varchar by every C_SIZE-th newline character
  30.         C_MAX_LEN        INT:=1024;                                --max length for item_txt; if change, also change item_txt varchar2(1024)
  31.         v_div_pos        int;                                        --end postion of each sub clob
  32.         v1_div_pos        int;                                        --end postion of each sub clob
  33.         v2_div_pos        int;                                        --end postion of each sub clob
  34.         v3_div_pos        int;                                        --end postion of each sub clob
  35.         v4_div_pos        int;                                        --end postion of each sub clob
  36.         v5_div_pos        int;                                        --end postion of each sub clob
  37.         v1_substr        varchar2(4000);
  38.         v2_substr        varchar2(4000);
  39.         v3_substr        varchar2(4000);
  40.         v4_substr        varchar2(4000);
  41.         v5_substr        varchar2(4000);
  42.     begin
  43.         loop
  44.             fetch p into in_rec;  -- input row
  45.             exit when p%NOTFOUND;
  46.             out_rec.id                :=in_rec.id;

  47.                         --if lines=C_SIZE, then dichotomy
  48.                         if in_rec.lines=C_SIZE then
  49.                         v_div_pos        :=instr(in_rec.text, C_NL_TERM, 1, C_SIZE/2);
  50.                         for a in 1..2 loop
  51.                                 v1_substr        :=substr(in_rec.text, 1+v_div_pos*(a-1), v_div_pos*(2-a)+1e6*(a-1));
  52.                                 v1_div_pos        :=instr(v1_substr, C_NL_TERM, 1, C_SIZE/4);
  53.                                 for b in 1..2 loop
  54.                                         v2_substr        :=substr(v1_substr, 1+v1_div_pos*(b-1), v1_div_pos*(2-b)+1e6*(b-1));
  55.                                         v2_div_pos        :=instr(v2_substr, C_NL_TERM, 1, C_SIZE/8);
  56.                                         for c in 1..2 loop
  57.                                                 v3_substr        :=substr(v2_substr, 1+v2_div_pos*(c-1), v2_div_pos*(2-c)+1e6*(c-1));
  58.                                                 v3_div_pos        :=instr(v3_substr, C_NL_TERM, 1, C_SIZE/16);
  59.                                                 for d in 1..2 loop
  60.                                                         v4_substr        :=substr(v3_substr, 1+v3_div_pos*(d-1), v3_div_pos*(2-d)+1e6*(d-1));
  61.                                                         v4_div_pos        :=instr(v4_substr, C_NL_TERM, 1, C_SIZE/32);
  62.                                                         for e in 1..2 loop
  63.                                                                 v5_substr        :=substr(v4_substr, 1+v4_div_pos*(e-1), v4_div_pos*(2-e)+1e6*(e-1));
  64.                                                                 v5_div_pos        :=instr(v5_substr, C_NL_TERM, 1, 1);
  65.                                                                 for f in 1..2 loop
  66.                                                                         out_rec.item_txt        :=substr(v5_substr, 1+v5_div_pos*(f-1), (v5_div_pos-1)*(2-f)+(v4_div_pos-v5_div_pos-1)*(f-1));
  67.                                                                         exit when out_rec.item_txt is null;
  68.                                                                         pipe row(out_rec);
  69.                                                                 end loop;
  70.                                                         end loop;
  71.                                                 end loop;
  72.                                         end loop;
  73.                                 end loop;
  74.                         end loop;
  75.                         --if lines=C_SIZE, then ordinary loop method,
  76.                         else
  77.                                 v_div_pos:=0;
  78.                                 v1_div_pos:=0;
  79.                                 for i in 1..1000000000 loop
  80.                         v1_div_pos:=instr(in_rec.text, C_NL_TERM, v_div_pos+1);
  81.                                         exit when v1_div_pos=0;
  82.                         out_rec.item_txt        :=substr(in_rec.text, v_div_pos+1, v1_div_pos-v_div_pos-1);
  83.                         v_div_pos                        :=v1_div_pos;
  84.                         pipe row(out_rec);
  85.                     end loop;
  86.                         end if;
  87.         end loop;
  88.         close p;
  89.         return;
  90.     end f_cartesian;

  91.     function f_cartesian2 (p refcur_t) return outrecset pipelined
  92.     parallel_enable (partition p by any)
  93.     is
  94.         in_rec      p%ROWTYPE;
  95.         out_rec     outrec_typ;
  96.     begin
  97.         loop
  98.             fetch p into in_rec;  -- input row
  99.             exit when p%NOTFOUND;
  100.             out_rec.id                        :=in_rec.id;
  101.                         out_rec.item_txt        :=in_rec.text;
  102.                         for i in 1..in_rec.lines loop
  103.                                 pipe row(out_rec);
  104.                         end loop;
  105.         end loop;
  106.         close p;
  107.         return;
  108.     end f_cartesian2;

  109.     function f_cartesian3 (p refcur_t) return outrecset pipelined
  110.     parallel_enable (partition p by any)
  111.     is
  112.         in_rec      p%ROWTYPE;
  113.         out_rec     outrec_typ;
  114.     begin
  115.         loop
  116.             fetch p into in_rec;  -- input row
  117.             exit when p%NOTFOUND;
  118.             out_rec.id                        :=in_rec.id;
  119.                         out_rec.item_txt        :=substr(in_rec.text, 1, 64);
  120.                         for i in 1..in_rec.lines loop
  121.                                 pipe row(out_rec);
  122.                         end loop;
  123.         end loop;
  124.         close p;
  125.         return;
  126.     end f_cartesian3;

  127. end refcur_pkg;
  128. /
複製代碼
  1. bill@ORCL> select       id, item_txt
  2.   2  from       table(refcur_pkg.f_cartesian(cursor(
  3.   3                     select  id, length(text)-length(replace(text, chr(10))), text
  4.   4                     from    t1
  5.   5                     )));

  6. 已選擇 12800 行。

  7. 已用時間:  00: 00: 00.40

  8. 執行計劃
  9. ----------------------------------------------------------
  10. Plan hash value: 4049074522

  11. --------------------------------------------------------------------------------------------------
  12. | Id  | Operation                          | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
  13. --------------------------------------------------------------------------------------------------
  14. |   0 | SELECT STATEMENT                   |             |  8168 |   271K|    29   (0)| 00:00:01 |
  15. |   1 |  VIEW                              |             |  8168 |   271K|    29   (0)| 00:00:01 |
  16. |   2 |   COLLECTION ITERATOR PICKLER FETCH| F_CARTESIAN |  8168 |       |    29   (0)| 00:00:01 |
  17. |   3 |    TABLE ACCESS FULL               | T1          |   186 |   366K|    68   (0)| 00:00:01 |
  18. --------------------------------------------------------------------------------------------------

  19. Note
  20. -----
  21.    - dynamic statistics used: dynamic sampling (level=2)


  22. 統計信息
  23. ----------------------------------------------------------
  24.         258  recursive calls
  25.           0  db block gets
  26.         469  consistent gets
  27.           1  physical reads
  28.           0  redo size
  29.      228399  bytes sent via SQL*Net to client
  30.        9927  bytes received via SQL*Net from client
  31.         855  SQL*Net roundtrips to/from client
  32.           0  sorts (memory)
  33.           0  sorts (disk)
  34.       12800  rows processed
複製代碼
我不太想詳細解釋二分法的代碼,因爲它太醜了,而代碼本身的邏輯其實很簡單。
因爲在我的處理中,是要從clob進行打散,所以我先把之前的處理結果分成64行爲一個大的varchar,到這裏再用硬性的6層二分法,
如果你的需求中行數不定,可以略微更改一下if條件,以及二分法的層數,這樣可以不必在二分法的層數上太過傷神。
總之,我們看到了性能的提升,是顯著的,而且,在我的測試當中,我發現先把instr的變量存起來,
也就是類似substr(in_rec.text, v_nl_pos+1, v_tmp_pos-v_nl_pos-1)這種寫法,要更高效,上面的代碼其實還可以再改。

純粹爲了測試,我還做了f_cartesian2和f_cartesian3,
前者是簡單的實現數據的複製,後者是每行取指定的substr,在這兩種情況下,其實plsql就沒有什麼優勢了。
  1. --sql connect by
  2. bill@ORCL> select       id, text
  3.   2  from       t1
  4.   3  connect by prior id=id and level<=length(text)-length(replace(text, chr(10))) and prior dbms_random.value>0;

  5. 已選擇 12800 行。

  6. 已用時間:  00: 00: 06.81

  7. 執行計劃
  8. ----------------------------------------------------------
  9. Plan hash value: 3874795171

  10. -------------------------------------------------------------------------------------
  11. | Id  | Operation                    | Name | Rows  | Bytes | Cost (%CPU)| Time     |
  12. -------------------------------------------------------------------------------------
  13. |   0 | SELECT STATEMENT             |      |   186 |   366K|    68   (0)| 00:00:01 |
  14. |*  1 |  CONNECT BY WITHOUT FILTERING|      |       |       |            |          |
  15. |   2 |   TABLE ACCESS FULL          | T1   |   186 |   366K|    68   (0)| 00:00:01 |
  16. -------------------------------------------------------------------------------------

  17. Predicate Information (identified by operation id):
  18. ---------------------------------------------------

  19.    1 - access("ID"=PRIOR "ID")
  20.        filter(LEVEL<=LENGTH("TEXT")-LENGTH(REPLACE("TEXT",' ')) AND PRIOR
  21.               "DBMS_RANDOM"."VALUE"()>0)

  22. Note
  23. -----
  24.    - dynamic statistics used: dynamic sampling (level=2)


  25. 統計信息
  26. ----------------------------------------------------------
  27.           4  recursive calls
  28.           0  db block gets
  29.         313  consistent gets
  30.           0  physical reads
  31.           0  redo size
  32.      236099  bytes sent via SQL*Net to client
  33.        9927  bytes received via SQL*Net from client
  34.         855  SQL*Net roundtrips to/from client
  35.           1  sorts (memory)
  36.           0  sorts (disk)
  37.       12800  rows processed

  38. --sql笛卡爾
  39. bill@ORCL> select       id, text
  40.   2  from       t1,
  41.   3  (select rownum n from dual connect by rownum<=64) b;

  42. 已選擇 12800 行。

  43. 已用時間:  00: 00: 05.00

  44. 執行計劃
  45. ----------------------------------------------------------
  46. Plan hash value: 894562235

  47. ----------------------------------------------------------------------------------------
  48. | Id  | Operation                       | Name | Rows  | Bytes | Cost (%CPU)| Time     |
  49. ----------------------------------------------------------------------------------------
  50. |   0 | SELECT STATEMENT                |      |   186 |   366K|    70   (0)| 00:00:01 |
  51. |   1 |  MERGE JOIN CARTESIAN           |      |   186 |   366K|    70   (0)| 00:00:01 |
  52. |   2 |   VIEW                          |      |     1 |       |     2   (0)| 00:00:01 |
  53. |   3 |    COUNT                        |      |       |       |            |          |
  54. |*  4 |     CONNECT BY WITHOUT FILTERING|      |       |       |            |          |
  55. |   5 |      FAST DUAL                  |      |     1 |       |     2   (0)| 00:00:01 |
  56. |   6 |   BUFFER SORT                   |      |   186 |   366K|    70   (0)| 00:00:01 |
  57. |   7 |    TABLE ACCESS FULL            | T1   |   186 |   366K|    68   (0)| 00:00:01 |
  58. ----------------------------------------------------------------------------------------

  59. Predicate Information (identified by operation id):
  60. ---------------------------------------------------

  61.    4 - filter(ROWNUM<=64)

  62. Note
  63. -----
  64.    - dynamic statistics used: dynamic sampling (level=2)


  65. 統計信息
  66. ----------------------------------------------------------
  67.           7  recursive calls
  68.           0  db block gets
  69.         378  consistent gets
  70.           0  physical reads
  71.           0  redo size
  72.      280133  bytes sent via SQL*Net to client
  73.        9927  bytes received via SQL*Net from client
  74.         855  SQL*Net roundtrips to/from client
  75.           2  sorts (memory)
  76.           0  sorts (disk)
  77.       12800  rows processed

  78. --管道表函數
  79. bill@ORCL> select       id, item_txt
  80.   2  from       table(refcur_pkg.f_cartesian2(cursor(
  81.   3                     select  id, length(text)-length(replace(text, chr(10))), text
  82.   4                     from    t1
  83.   5                     )));

  84. 已選擇 12800 行。

  85. 已用時間:  00: 00: 05.16

  86. 執行計劃
  87. ----------------------------------------------------------
  88. Plan hash value: 2991304848

  89. ---------------------------------------------------------------------------------------------------
  90. | Id  | Operation                          | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
  91. ---------------------------------------------------------------------------------------------------
  92. |   0 | SELECT STATEMENT                   |              |  8168 |   271K|    29   (0)| 00:00:01 |
  93. |   1 |  VIEW                              |              |  8168 |   271K|    29   (0)| 00:00:01 |
  94. |   2 |   COLLECTION ITERATOR PICKLER FETCH| F_CARTESIAN2 |  8168 |       |    29   (0)| 00:00:01 |
  95. |   3 |    TABLE ACCESS FULL               | T1           |   186 |   366K|    68   (0)| 00:00:01 |
  96. ---------------------------------------------------------------------------------------------------

  97. Note
  98. -----
  99.    - dynamic statistics used: dynamic sampling (level=2)


  100. 統計信息
  101. ----------------------------------------------------------
  102.         266  recursive calls
  103.           0  db block gets
  104.         529  consistent gets
  105.           0  physical reads
  106.           0  redo size
  107.      236103  bytes sent via SQL*Net to client
  108.        9927  bytes received via SQL*Net from client
  109.         855  SQL*Net roundtrips to/from client
  110.           0  sorts (memory)
  111.           0  sorts (disk)
  112.       12800  rows processed


  113. bill@ORCL> select       id, item_txt
  114.   2  from       table(refcur_pkg.f_cartesian3(cursor(
  115.   3                     select  id, length(text)-length(replace(text, chr(10))), text
  116.   4                     from    t1
  117.   5                     )));

  118. 已選擇 12800 行。

  119. 已用時間:  00: 00: 00.29

  120. 執行計劃
  121. ----------------------------------------------------------
  122. Plan hash value: 2831580648

  123. ---------------------------------------------------------------------------------------------------
  124. | Id  | Operation                          | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
  125. ---------------------------------------------------------------------------------------------------
  126. |   0 | SELECT STATEMENT                   |              |  8168 |   271K|    29   (0)| 00:00:01 |
  127. |   1 |  VIEW                              |              |  8168 |   271K|    29   (0)| 00:00:01 |
  128. |   2 |   COLLECTION ITERATOR PICKLER FETCH| F_CARTESIAN3 |  8168 |       |    29   (0)| 00:00:01 |
  129. |   3 |    TABLE ACCESS FULL               | T1           |   186 |   366K|    68   (0)| 00:00:01 |
  130. ---------------------------------------------------------------------------------------------------

  131. Note
  132. -----
  133.    - dynamic statistics used: dynamic sampling (level=2)


  134. 統計信息
  135. ----------------------------------------------------------
  136.         278  recursive calls
  137.           0  db block gets
  138.         555  consistent gets
  139.           0  physical reads
  140.         124  redo size
  141.      228407  bytes sent via SQL*Net to client
  142.        9927  bytes received via SQL*Net from client
  143.         855  SQL*Net roundtrips to/from client
  144.           0  sorts (memory)
  145.           0  sorts (disk)
  146.       12800  rows processed
複製代碼
整個這些測試,比較,對我震撼是比較大的。用sql來實現笛卡爾然後取不同的數據,似乎是個天經地義適合sql來處理的事情,可惜到最後我都不知道這究竟是不適合sql,還是oracle沒把這個實現好?大家不妨用別的數據庫來測測?
不過,從此以後,oracle裏面,再有這種需求,大家不妨參考這裏plsql的思路來做。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章