Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1082470
  • 博文数量: 145
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 3510
  • 用 户 组: 普通用户
  • 注册时间: 2011-03-30 13:00
个人简介

About me:Oracle ACE,optimistic,passionate and harmonious. Focus on oracle programming,peformance tuning,db design, j2ee,Linux/AIX,web2.0 tech,etc

文章分类

全部博文(145)

文章存档

2023年(28)

2022年(43)

2020年(62)

2014年(3)

2013年(9)

分类: Oracle

2020-06-09 09:15:18

接PART2:
7.4 使用集合构造伪表

  对这类问题的常规解法,比如Oracle版本是9i,可以使用PL/SQL中的集合类型,对传入的字符串按分隔符解析之后存储到相关集合类型的变量中,比如可以存储到嵌套表,数组中(注意不能是INDEX BY表,必须是SCHEMA级别的类型,数组有容量也不常使用),然后利用TABLE函数将集合转为伪表,剩下就和前面说的一样了。


  试想一下,使用集合构造临时表,需要做哪些工作呢?

1)  外界传入的是一个含有分隔符(一般是逗号,确保字段中没有逗号,如果有,用其他分隔符)的字符串,比如’aa,bb,cc’之类的字符串,首先需要按分隔符解析,然后将每个值存储到对应的集合变量中,所以,需要有一个函数能够接收传入的字符串,然后解析并存储到相应的集合变量并且返回。

2)  将集合变量通过TABLE函数转换为伪表,放到子查询中。table函数将集合转为伪表,返回的列名是COLUMN_VALUE,对应的类型是集合元素的类型,本节例子的COLUMN_VALUE的类型就是VARCHAR2类型。

3)  然后写相应的查询语句。


根据上面的描述,需要创建嵌套表以及将字符串转为嵌套表的函数,如下所示:


--创建嵌套表

CREATE OR REPLACE TYPE varchar2_tt AS TABLE

     OF VARCHAR2 (1000);

/

--创建函数

CREATE OR REPLACE

     FUNCTION f_str2list( in_str IN VARCHAR2 ,in_delimiter IN VARCHAR2 DEFAULT ',' )

 RETURN varchar2_tt

/******************************************************************************

||程序说明:将按指定分隔符分割的字符串转为嵌套表类型变量返回

||输入变量:

||             in_str 字符串,如'a,b,c'

||             in_delimiter 分割符,默认是逗号

||输出变量:

||             varchar2_tt类型,嵌套表

******************************************************************************/

  AS

        v_str          VARCHAR2(32767) DEFAULT in_str || in_delimiter;

        v_result       varchar2_tt := varchar2_tt();

        i               NUMBER;

    BEGIN

     LOOP

         EXIT WHEN v_str IS NULL;

          i := INSTR( v_str, in_delimiter );

         v_result.extend;

         v_result(v_result.count) :=

               TRIM( SUBSTR( v_str, 1,  i -1 ) );

         v_str := SUBSTR( v_str,  i +1 );

    ENDLOOP;

    RETURN v_result;

END;

/


  执行上面的脚本,创建嵌套表和返回嵌套表的函数即可。下面使用上面创建的嵌套表和函数来解决
where in list的问题。通过函数将传入的字符串包装成嵌套表,然后利用TABLE函数将嵌套表转为伪表,放到子查询中即可。具体操作如下:


DINGJUN123>VAR str VARCHAR2(100);

DINGJUN123>EXEC :str :='XY,YZ';


PL/SQL 过程已成功完成。

  2  FROM t

  3  WHERE object_name IN

  4  (SELECT column_value

  5  FROM TABLE( CAST(f_str2list(:str) AS varchar2_tt )

  6            )

  7  );


  COUNT(*)

----------

         2


已选择 1 行。

 
  结果是正确的。如果传入的不是字符串,而是一个SQL语句或REF CURSOR变量,可以吗?当然可以,把f_str2list函数改改就可以了,这个读者可以自己思考一下,这里不详细讲解。

7.5 Where In List性能问题

  Where in List问题要特别注意性能问题,一般选择的字段都建有索引,希望计划走索引和nested loop方式连接查询,而不希望通过hash joinsort merge join方式连接查询,因为实际中传入的一般都不是很长的字符串,而源表可能数据量很大。本部分主要探讨使用集合函数解决where in list问题中注意的相关问题,对于正则表达式和INSTR+SUBSTR也可以通过hint来固定计划,而且一般不使用hint,CBO也能选择正确的计划,比较简单,所以只探讨集合函数的性能问题,因为使用TABLE函数还是有点复杂的,请看:


DINGJUN123>SELECT COUNT(*) FROM t;


  COUNT(*)

----------

     14006


已选择 1 行。


DINGJUN123>SET AUTOTRACE TRACEONLY

DINGJUN123>VAR str VARCHAR2(100);

DINGJUN123>EXEC :str :='XY,YZ';


PL/SQL 过程已成功完成。


DINGJUN123> SELECT *

  2     FROM t

  3     WHERE object_name IN

  4     (SELECT column_value

  5       FROM TABLE( CAST( f_str2list(:str) AS varchar2_tt ))

  6     );


已选择2行。



执行计划

----------------------------------------------------------

Plan hash value: 3487633200


----------------------------------------------------------------------------------------------

| Id  | Operation                          | Name       | Rows  | Bytes | Cost (%CPU)| Time     |

----------------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT                   |            |     1 |    88 |    84   (3)| 00:00:02 |

|*  1 |  HASH JOIN RIGHT SEMI             |            |     1 |    88 |    84   (3)| 00:00:02 |

|   2 |   COLLECTION ITERATOR PICKLER FETCH| F_STR2LIST |       |       |            |          |

|   3 |   TABLE ACCESS FULL               | T          | 14006 |  1176K|    54   (2)| 00:00:01 |

----------------------------------------------------------------------------------------------


Predicate Information (identified by operation id):

---------------------------------------------------


   1 - access("OBJECT_NAME"=VALUE(KOKBF$))



统计信息

----------------------------------------------------------

        927  recursive calls

          0  db block gets

        486  consistent gets

        233  physical reads

          0  redo size

       1257  bytes sent via SQL*Net to client

        384  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          8  sorts (memory)

          0  sorts (disk)

          2  rows processed

   

    第1次硬解析,为了比较多执行几次,直到逻辑读固定,统计信息为:


统计信息

----------------------------------------------------------

          0  recursive calls

          0  db block gets

        184  consistent gets

          0  physical reads

          0  redo size

       1257  bytes sent via SQL*Net to client

        384  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

          2  rows processed


  从上面结果看到,上面SQL采用的是Hash join的连接方式,全表访问表t,第1次执行逻辑读很大,为486,最终逻辑读固定为184,平均每行逻辑读为92(184/2),这种计划是很差的。那为什么Oracle会采用这种计划呢?如下分析:


DINGJUN123>SELECT/*+first_rows*/ *

  2  FROM TABLE( CAST( f_str2list(:str) AS varchar2_tt ));


已选择2行。



执行计划

----------------------------------------------------------

Plan hash value: 2025561284


----------------------------------------------------------------------------------------------

| Id  | Operation                         | Name       | Rows  | Bytes | Cost (%CPU)| Time     |

----------------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT                  |            |  8168 | 16336 |    29   (0)| 00:00:01 |

|   1 |  COLLECTION ITERATOR PICKLER FETCH| F_STR2LIST |       |       |            |          |

----------------------------------------------------------------------------------------------


  从上面结果看出,TABLE函数的默认行数是8168(TABLE函数创建的伪表是没有统计信息的),这个值不小了,一般比实际应用中的行数要多的多,经常导致执行计划走hash join,而不是nested loop+index(其实这个默认的TABLE函数基数8168依赖于数据库的数据块大小,我的数据库数据块是8K的,CBO估算TABLE函数的基数为8168)

  
怎么改变这种情况呢?当然是加hint提示来改变执行计划了,where in list,常常使用的hint有:first_rowsindexcardinality等。这里特别介绍下cardinality(table|alias,n),这个hint很有用,它可以让CBO优化器认为表的行数是n,这样就可以改变执行计划了。现在改写上面的查询:


DINGJUN123>SELECT/*+cardinality(tab,5)*/ column_value

  2  FROM TABLE( CAST( f_str2list(:str) AS varchar2_tt )) tab;


已选择2行。



执行计划

----------------------------------------------------------

Plan hash value: 2025561284


----------------------------------------------------------------------------------------------

| Id  | Operation                         | Name       | Rows  | Bytes | Cost (%CPU)| Time     |

----------------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT                  |            |     5 |    10 |    29   (0)| 00:00:01 |

|   1 |  COLLECTION ITERATOR PICKLER FETCH| F_STR2LIST |       |       |            |          |

--------------------------------------------------------------------------------------------

    

--看上面CBO能估算基数为5了,下面试试

DINGJUN123> SELECT *

  2         FROM t

  3         WHERE object_name IN

  4         (SELECT /*+cardinality(tab,5)*/ column_value

  5           FROM TABLE( CAST( f_str2list(:str) AS varchar2_tt )) tab

  6         );


已选择2行。



执行计划

----------------------------------------------------------

Plan hash value: 4129437246


----------------------------------------------------------------------------------------------

| Id  | Operation                            | Name       | Rows  | Bytes | Cost (%CPU)| Time

----------------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT                     |            |     6 |   528 |    36   (3)| 00:00:0

|   1 |  TABLE ACCESS BY INDEX ROWID         | T          |     1 |    86 |     2   (0)| 00:00:0

|   2 |   NESTED LOOPS                       |            |     6 |   528 |    36   (3)| 00:00:0

|   3 |    SORT UNIQUE                       |            |       |       |            |

|   4 |     COLLECTION ITERATOR PICKLER FETCH| F_STR2LIST |       |       |            |

|*  5 |    INDEX RANGE SCAN                  | IDX_T      |     1 |       |     1   (0)| 00:00:0

----------------------------------------------------------------------------------------------


Predicate Information (identified by operation id):

---------------------------------------------------


   5 - access("OBJECT_NAME"=VALUE(KOKBF$))

统计信息

----------------------------------------------------------

        590  recursive calls

          0  db block gets

        149  consistent gets

         14  physical reads

          0  redo size

       1257  bytes sent via SQL*Net to client

        384  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          6  sorts (memory)

          0  sorts (disk)

          2  rows processed


    多执行几次,直到逻辑读固定的统计信息为:


统计信息

----------------------------------------------------------

          0  recursive calls

          0  db block gets

          7  consistent gets

          0  physical reads

          0  redo size

       1257  bytes sent via SQL*Net to client

        384  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          1  sorts (memory)

          0  sorts (disk)

          2  rows processed


  第1次逻辑读为149,比前面hash join的软解析逻辑读还要少(184),而且最后逻辑读固定为7,则平均每行逻辑读为3.5,效率很好。现在计划走nested loop了,而且对表t也走了索引。

  还需要注意点,使用
TABLE函数解决where in list问题,常需要在子查询中加ROWNUM条件,从而固化子查询为视图,上面的在10g R2环境下测试加ROWNUM和不加ROWNUM计划不同,但是最后的逻辑读和函数调用次数是一样的,只不过计划不同而已,加ROWNUM执行计划和固定逻辑读后的统计信息为(初次执行的省略)


DINGJUN123> SELECT *

  2             FROM t

  3             WHERE object_name IN

  4             (SELECT/*+cardinality(tab,5)*/ column_value

  5               FROM TABLE( CAST( f_str2list(:str) AS varchar2_tt )) tab

  6             WHERE ROWNUM >=0

  7             );


已选择2行。


执行计划

----------------------------------------------------------

Plan hash value: 483176403


----------------------------------------------------------------------------------------------

| Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)| Time|

----------------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT             |            |     1 |   588 |    32   (4)| 00:00:01|

|   1 |  TABLE ACCESS BY INDEX ROWID | T          |     1 |    86 |     2   (0)| 00:00:01|

|   2 |   NESTED LOOPS               |            |     1 |   588 |    32   (4)| 00:00:01|

|   3 |    VIEW                      | VW_NSO_1   |     5 |  2510 |    29   (0)| 00:00:01|

|   4 |     HASH UNIQUE              |            |     1 |    10 |            |         |

|   5 |      COUNT                   |            |       |       |            |         |

|*  6 |       FILTER                 |            |       |       |            |         |

|   7 |        COLLECTION ITERATOR PICKLER FETCH| F_STR2LIST |       |       |  |        |

|*  8 |    INDEX RANGE SCAN          | IDX_T      |     1 |       |     1   (0)| 00:00:01 |

----------------------------------------------------------------------------------------------


Predicate Information (identified by operation id):

---------------------------------------------------


   6 - filter(ROWNUM>=0)

   8 - access("OBJECT_NAME"="$nso_col_1")



统计信息

----------------------------------------------------------

          0  recursive calls

          0  db block gets

          7  consistent gets

          0  physical reads

          0  redo size

       1257  bytes sent via SQL*Net to client

        384  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

          2  rows processed


  可见在10g R2下区别不大,也就是执行计划不同而已,最后逻辑读都一样,函数调用的次数也一样,这里也只能代表我的测试环境上是如此,以前碰到过不同的情况,下面就改变测试环境,测试加ROWNUM和不加ROWNUM的区别,然后给出一点建议。这个测试在9i下完成


SQL>SHOW RELEASE

release 902000400


  建表语句以及F_STR2LIST函数都和10g的一样,只不过表t的记录数不同,为了比较加ROWNUM的好处,需要在函数F_STR2LISTBEGIN后面加上:


DBMS_APPLICATION_INFO.set_client_info(USERENV('client_info')+1 );//用来测试函数的调用次数


  如果对这对
DBMS_APPLICATION_INFO包不是很熟悉,建议查看Oracle文档了解,这个还是很有用的。
  
建立表、类型、函数(请读者自己完成),查看表t的数目:


SQL> SELECT COUNT(*) FROM t;


  COUNT(*)

----------

      9127


  表
t9127行,下面详细看测试过程,先不加ROWNUM


SQL>VAR str VARCHAR2(100);

SQL>EXEC :str :='XY,YZ';


PL/SQL 过程已成功完成。


--重置client_info

SQL> EXEC DBMS_APPLICATION_INFO.set_client_info(0);


PL/SQL 过程已成功完成。


已用时间:  00: 00: 00.07

SQL> SET AUTOTRACE TRACEONLY

SQL>  SELECT *

  2             FROM t

  3             WHERE object_name IN

  4             (SELECT/*+cardinality(tab,5)*/  column_value

  5               FROM TABLE( CAST( f_str2list(:str) AS varchar2_tt )) tab

  6             );


已用时间:  00: 00: 02.76


执行计划

-------------------------------------------------------------------

   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=100410 Card=1 Bytes=83)

   1    0   NESTED LOOPS (SEMI) (Cost=100410 Card=1 Bytes=83)

   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=13 Card=9127 Bytes=757541)

   3    1     COLLECTION ITERATOR (PICKLER FETCH) OF 'F_STR2LIST'



统计信息

----------------------------------------------------------

       4320  recursive calls

          0  db block gets

       1042  consistent gets

        216  physical reads

          0  redo size

        732  bytes sent via SQL*Net to client

        233  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

        109  sorts (memory)

          0  sorts (disk)

          2  rows processed


SQL> SET AUTOTRACE OFF

SQL> SELECT USERENV('client_info') FROM DUAL;


USERENV('CLIENT_INFO')

----------------------------------------------------------------

9127


已用时间:  00: 00: 00.03


  从上面的测试看出,这个计划多次调用了F_STR2LIST函数,表t9127行,函数就被调用了9127次,这是不可接受的,而且全表扫描t(开始的10g R2中我的测试是走索引了),也是不可接受的,应该走索引才对,可能CBO估算错误,这个cardinality在这个测试中没有起作用,这个计划最终逻辑读固定为118,那么平均每行逻辑读为59,性能也是很差的,受多次调用函数以及不走索引的影响,这个查询最终逻辑读固定的统计信息为:


统计信息

----------------------------------------------------------

          0  recursive calls

          0  db block gets

        118  consistent gets

          0  physical reads

          0  redo size

        731  bytes sent via SQL*Net to client

        233  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

          2  rows processed


下面测试加rownum的情况:


--重置client_info

SQL> EXEC DBMS_APPLICATION_INFO.set_client_info(0);

PL/SQL 过程已成功完成。


已用时间:  00: 00: 00.07



SQL> SELECT *

  2             FROM t

  3             WHERE object_name IN

  4             (SELECT/*+cardinality(tab,5)*/  column_value

  5               FROM TABLE( CAST( f_str2list(:str) AS varchar2_tt )) tab

  6              WHERE ROWNUM >=0

  7             );


已用时间:  00: 00: 00.79


执行计划

----------------------------------------------------------

   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=32 Card=1 Bytes=585)

   1    0   HASH JOIN (SEMI) (Cost=32 Card=1 Bytes=585)

   2    1     TABLE ACCESS (FULL) OF 'T' (Cost=13 Card=9127 Bytes=757541)

   3    1     VIEW OF 'VW_NSO_1' (Cost=11 Card=5 Bytes=2510)

   4    3       COUNT

   5    4         FILTER

   6    5           COLLECTION ITERATOR (PICKLER FETCH) OF 'F_STR2LIST

          '

统计信息

----------------------------------------------------------

         30  recursive calls

          0  db block gets

        125  consistent gets

          1  physical reads

          0  redo size

        732  bytes sent via SQL*Net to client

        233  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

          2  rows processed


SQL> SET AUTOTRACE OFF

SQL> SELECT USERENV('client_info') FROM DUAL;


USERENV('CLIENT_INFO')

----------------------------------------------------------------

1


已用时间:  00: 00: 00.03


  加了ROWNUM,子查询被固化为视图,函数只调用一次,现在的逻辑读为125,比不加ROWNUM1042要小。但是还是全表扫描,影响效率。下面看固定下来的逻辑读数目为117


统计信息

----------------------------------------------------------

          0  recursive calls

          0  db block gets

        117  consistent gets

          0  physical reads

          0  redo size

        731  bytes sent via SQL*Net to client

        233  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

          2  rows processed


  平均每行逻辑读为58.5,还是效率很低,虽然这里最终逻辑读和不加ROWNUM的最终逻辑读差不多,但是多次函数调用是不可接受的,这里的函数简单而且表数据量不是很大,如果字符串比较长表数据量很大,效率就低了。有的时候,在9i下测试,加了ROWNUM,就走索引了,也就是cardinality起作用了,比如我在10g R2下的测试就走了索引,但是这里的测试仍然没有起作用,导致还是走全表扫描,所以优化还是和环境有很大关系,必须做足测试。

  所以,还是加上
ROWNUM固化子查询,减少函数调用次数,并且调整查询走index,最好是nested loop形式。上面因为全表扫描t,导致性能降低,下面加上索引hintROWNUM,也需要cardinality在这个环境下,我做过几个测试,单独使用index hint效果不好,计划走HASH JOIN,逻辑读很大,但是单独使用first_rows hint就可以走NESTED LOOPS,这里就不提供测试过程了),再次测试(index hintcardinality hint一起使用)


SQL>  SELECT/*+index(t idx_t)*/ *

  2             FROM t

  3             WHERE object_name IN

  4             (SELECT/*+cardinality(tab,5)*/  column_value

  5               FROM TABLE( CAST( f_str2list(:str) AS varchar2_tt )) tab

  6              WHERE ROWNUM >=0

  7             );


已用时间:  00: 00: 00.73


执行计划

----------------------------------------------------------

   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=37 Card=6 Bytes=3510)

   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=2 Card=1 Bytes=83)

   2    1     NESTED LOOPS (Cost=37 Card=6 Bytes=3510)

   3    2       VIEW OF 'VW_NSO_1' (Cost=11 Card=5 Bytes=2510)

   4    3         SORT (UNIQUE)

   5    4           COUNT

   6    5             FILTER

   7    6               COLLECTION ITERATOR (PICKLER FETCH) OF 'F_STR2LIST'

   8    2       INDEX (RANGE SCAN) OF 'IDX_T' (NON-UNIQUE) (Cost=1 Card=1)


统计信息

----------------------------------------------------------

          0  recursive calls

          0  db block gets

         13  consistent gets

          2  physical reads

          0  redo size

        732  bytes sent via SQL*Net to client

        233  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          1  sorts (memory)

          0  sorts (disk)

          2  rows processed



  最终固定下的逻辑读锁定为
7,每行平均逻辑读为3.5,和10g上测试的一样了,性能得到提升。如下:


统计信息

----------------------------------------------------------

          0  recursive calls

          0  db block gets

          7  consistent gets

          0  physical reads

          0  redo size

        732  bytes sent via SQL*Net to client

        233  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

          1  sorts (memory)

          0  sorts (disk)

          2  rows processed


  综上所述,使用TABLE函数解决where in list问题,常常需要走nested loop+索引形式,常使用hintfirst_rowsindexcardinality等,有时候cardinality可能不起作用,需要其它的hint,比如index,first_rows等,有时候需要几个hint配合使用,而且需要加上ROWNUM固化子查询,这样一般可以获得最大性能。

7.6 Where In List问题总结

  本章对where in list问题进行了详细的探讨,下面给出一些总结:

1) 实现where in list问题,首先需要使传入的不定长参数能够作为绑定变量,其次就是要按分隔符解析字符串为多行,然后通过JOINEXISTSIN子查询等解决where in list问题。


2) 一般的方法就是使用PL/SQL集合+TABLE函数来解决where in list问题,要注意,除非你特别确定,不会多次调用函数,否则加上ROWNUM条件有备无患,另外常常需要通过hint调整查询,有时候需要1hint就可以,有时候可能需要多个hint,比如cardinality+index配合使用,对于这种查询的性能测试,必须了解自己的源表数据量情况以及传入的参数情况,查询的结果行数等,做足测试,从而获得最大的性能。上面写的函数参数最大支持32767字节,如果需要更大的字节支持,比如CLOB,那么可以考虑使用临时表,如果还用函数就比较麻烦,当然使用正则表达式或INSTR+SUBSTR的方法是支持CLOB的。


310G中可以考虑使用正则表达式解决,10G以下的版本可以考虑使用INSTR+SUBSTR的方法。当然使用正则表达式或INSTR+SUBSTR方法可以考虑使用动态视图屏蔽查询复杂性,这种方法比较简单,一般也能获得很好的性能。


4一般来说,where in list是很复杂的,需要经过严格的测试,来确定何种方法适合你,比如本章说的正则和INSTR+SUBSTR的方法测试下来平均每行逻辑读为2,比集合的效率要稍微高点(集合最好的为3.5),但是一般我比较喜欢集合处理的方式,因为SQL写起来最简单,只需要定义函数和集合类型即可。读者可以自己经过测试后,选定何种方法解决。


5对于管道函数的基数预测,11GR2具有""特性,这个特性使CBO具有智能化自我学习功能,本章测试的TABLE函数的默认基数CBO认为是8168(因为我的数据库数据块大小是8K),但是到11GR2使用""特性就会改变这一情况了,也可以减少一堆hint的使用,其实很简单,这里就不测试了。如果你的环境是11GR2,可以到网上搜索Oracle 特性的相关文章来学习。



阅读(1187) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~