您的位置:首页 > 数据库 > Oracle

Oracle12c中功能及性能新特点之with子句的增强

2016-05-11 17:41 483 查看
1. 设置

创建测试表。

DROP TABLE test PURGE;

CREATE TABLE test AS

SELECT 1 AS id

FROM   dual

CONNECT BY level <= 1000000;

2. WITH子句中的函数

WITH子句声明部分可用来定义函数,如下所示。

WITH

  FUNCTION with_function(p_id IN NUMBER) RETURN NUMBER IS

  BEGIN

    RETURN p_id;

  END;

SELECT with_function(id)

FROM   test

WHERE  rownum = 1

/

WITH_FUNCTION(ID)

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

                1

SQL>

有意思的是,当WITH子句中包含PL/SQL声明时,分号";"不再能用作SQL语句的终止符。如果我们使用它,SQL*Plus会等待更多命令文本输入。即使在官方文档中,也是使用了分号“;”和反斜杠“/”的组合。 

从名字解析角度看,WITH子句PL/SQL声明部分定义的函数比当前模式中其他同名对象优先级要高。

3. WITH子句中的过程

即使不被使用,我们也可以在声明部分定义过程。

SET SERVEROUTPUT ON

WITH

  PROCEDURE with_procedure(p_id IN NUMBER) IS

  BEGIN

    DBMS_OUTPUT.put_line('p_id=' || p_id);

  END;

SELECT id

FROM   test

WHERE  rownum = 1

/

        ID

----------

         1

SQL>

现实中,如果你打算从声明部分的函数中调用一个过程,你可以在声明部分定义一个过程。

WITH

  PROCEDURE with_procedure(p_id IN NUMBER) IS

  BEGIN

    DBMS_OUTPUT.put_line('p_id=' || p_id);

  END;

  FUNCTION with_function(p_id IN NUMBER) RETURN NUMBER IS

  BEGIN

    with_procedure(p_id);

    RETURN p_id;

  END;

SELECT with_function(id)

FROM   test

WHERE  rownum = 1

/

WITH_FUNCTION(ID)

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

                1

p_id=1

SQL>

4. PL/SQL支持

PL/SQL并不支持该特点。如果视图在PL/SQL中使用将会报编译错误,如下所示。

BEGIN

  FOR cur_rec IN (WITH

                    FUNCTION with_function(p_id IN NUMBER) RETURN NUMBER IS

                    BEGIN

                      RETURN p_id;

                    END;

                  SELECT with_function(id)

                  FROM   test

                  WHERE  rownum = 1)

  LOOP

    NULL;

  END LOOP;

END;

/

FUNCTION with_function(p_id IN NUMBER) RETURN NUMBER IS

                             *

ERROR at line 3:

ORA-06550: line 3, column 30:

PL/SQL: ORA-00905: missing keyword

ORA-06550: line 2, column 19:

PL/SQL: SQL Statement ignored

ORA-06550: line 5, column 34:

PLS-00103: Encountered the symbol ";" when expecting one of the following:

loop

SQL>

使用动态SQL可以绕过这个限制。

SET SERVEROUTPUT ON

DECLARE

  l_sql     VARCHAR2(32767);

  l_cursor  SYS_REFCURSOR;

  l_value   NUMBER;

BEGIN

  l_sql := 'WITH

              FUNCTION with_function(p_id IN NUMBER) RETURN NUMBER IS

              BEGIN

                RETURN p_id;

              END;

            SELECT with_function(id)

            FROM   test

            WHERE  rownum = 1';

  

  OPEN l_cursor FOR l_sql;

  FETCH l_cursor INTO l_value;

  DBMS_OUTPUT.put_line('l_value=' || l_value);

  CLOSE l_cursor;

END;

/

l_value=1

PL/SQL procedure successfully completed.

SQL>

PL/SQL中将该特点用于静态SQL是未来版本的事情。

5. 性能优势

定义行内PL/SQL代码的原因是为了改善性能。下面创建常规函数来进行比较。

CREATE OR REPLACE FUNCTION normal_function(p_id IN NUMBER) RETURN NUMBER IS

BEGIN

  RETURN p_id;

END;

/

运行如下测试,测量行内函数查询消耗的时间和CPU。

SET SERVEROUTPUT ON

DECLARE

  l_time    PLS_INTEGER;

  l_cpu     PLS_INTEGER;

  

  l_sql     VARCHAR2(32767);

  l_cursor  SYS_REFCURSOR;

  

  TYPE t_tab IS TABLE OF NUMBER;

  l_tab t_tab;

BEGIN

  l_time := DBMS_UTILITY.get_time;

  l_cpu  := DBMS_UTILITY.get_cpu_time;

  l_sql := 'WITH

              FUNCTION with_function(p_id IN NUMBER) RETURN NUMBER IS

              BEGIN

                RETURN p_id;

              END;

            SELECT with_function(id)

            FROM   test';

            

  OPEN l_cursor FOR l_sql;

  FETCH l_cursor

  BULK COLLECT INTO l_tab;

  CLOSE l_cursor;

  

  DBMS_OUTPUT.put_line('WITH_FUNCTION  : ' ||

                       'Time=' || TO_CHAR(DBMS_UTILITY.get_time - l_time) || ' hsecs ' ||

                       'CPU Time=' || (DBMS_UTILITY.get_cpu_time - l_cpu) || ' hsecs ');

  l_time := DBMS_UTILITY.get_time;

  l_cpu  := DBMS_UTILITY.get_cpu_time;

  l_sql := 'SELECT normal_function(id)

            FROM   test';

            

  OPEN l_cursor FOR l_sql;

  FETCH l_cursor

  BULK COLLECT INTO l_tab;

  CLOSE l_cursor;

  

  DBMS_OUTPUT.put_line('NORMAL_FUNCTION: ' ||

                       'Time=' || TO_CHAR(DBMS_UTILITY.get_time - l_time) || ' hsecs ' ||

                       'CPU Time=' || (DBMS_UTILITY.get_cpu_time - l_cpu) || ' hsecs ');

 

END;

/

WITH_FUNCTION  : Time=45 hsecs CPU Time=39 hsecs

NORMAL_FUNCTION: Time=129 hsecs CPU Time=113 hsecs

PL/SQL procedure successfully completed.

SQL>

从该测试可以看到,行内函数值消耗了普通函数三分之一的时间和CPU。

6. PRAGMA UDF

12c 版本前,人们经常会提到PRAGMA UDF,据说可通过行内PL/SQL来提升性能,同时,允许在SQL语句外定义PL/SQL对象。下列代码用PRAGMA重新定义之前的常规函数。

CREATE OR REPLACE FUNCTION normal_function(p_id IN NUMBER) RETURN NUMBER IS

  PRAGMA UDF;

BEGIN

  RETURN p_id;

END;

/

一旦函数被编译,从先前部分运行该函数会产生相当有趣的结果。

SET SERVEROUTPUT ON

DECLARE

  l_time    PLS_INTEGER;

  l_cpu     PLS_INTEGER;

  

  l_sql     VARCHAR2(32767);

  l_cursor  SYS_REFCURSOR;

  

  TYPE t_tab IS TABLE OF NUMBER;

  l_tab t_tab;

BEGIN

  l_time := DBMS_UTILITY.get_time;

  l_cpu  := DBMS_UTILITY.get_cpu_time;

  l_sql := 'WITH

              FUNCTION with_function(p_id IN NUMBER) RETURN NUMBER IS

              BEGIN

                RETURN p_id;

              END;

            SELECT with_function(id)

            FROM   test';

            

  OPEN l_cursor FOR l_sql;

  FETCH l_cursor

  BULK COLLECT INTO l_tab;

  CLOSE l_cursor;

  

  DBMS_OUTPUT.put_line('WITH_FUNCTION  : ' ||

                       'Time=' || TO_CHAR(DBMS_UTILITY.get_time - l_time) || ' hsecs ' ||

                       'CPU Time=' || (DBMS_UTILITY.get_cpu_time - l_cpu) || ' hsecs ');

  l_time := DBMS_UTILITY.get_time;

  l_cpu  := DBMS_UTILITY.get_cpu_time;

  l_sql := 'SELECT normal_function(id)

            FROM   test';

            

  OPEN l_cursor FOR l_sql;

  FETCH l_cursor

  BULK COLLECT INTO l_tab;

  CLOSE l_cursor;

  

  DBMS_OUTPUT.put_line('NORMAL_FUNCTION: ' ||

                       'Time=' || TO_CHAR(DBMS_UTILITY.get_time - l_time) || ' hsecs ' ||

                       'CPU Time=' || (DBMS_UTILITY.get_cpu_time - l_cpu) || ' hsecs ');

 

END;

/

WITH_FUNCTION  : Time=44 hsecs CPU Time=40 hsecs

NORMAL_FUNCTION: Time=33 hsecs CPU Time=29 hsecs

PL/SQL procedure successfully completed.

SQL>

用PRAGMA UDF的独立函数似乎一直比行内函数还快。

我以为从PL/SQL中调用PRAGMA UDF定义的函数会失败,可事实似乎不是这么个情况。

DECLARE

  l_number NUMBER;

BEGIN

  l_number := normal_function(1);

END;

/

PL/SQL procedure successfully completed.

SQL>

7. WITH_PLSQL Hint

如果包含PL/SQL声明部分的查询不是顶级查询,那么,顶级查询必须包含WITH_PLSQL hint。没有该hint,语句在编译时会失败,如下所示。

UPDATE test a

SET a.id = (WITH

              FUNCTION with_function(p_id IN NUMBER) RETURN NUMBER IS

              BEGIN

                RETURN p_id;

              END;

            SELECT with_function(a.id)

            FROM   dual);

/

SET a.id = (WITH

            *

ERROR at line 2:

ORA-32034: unsupported use of WITH clause

SQL>

加上WITH_PLSQL hint后,语句编译通过且如期运行。

UPDATE /*+ WITH_PLSQL */ t1 a

SET a.id = (WITH

              FUNCTION with_function(p_id IN NUMBER) RETURN NUMBER IS

              BEGIN

                RETURN p_id;

              END;

            SELECT with_function(a.id)

            FROM   dual);

/

1000000 rows updated.

SQL>

8. DETERMINISTIC Hint

就像刘易斯指出的那样,WITH子句中使用函数会阻止发生DETERMINISTIC优化。

SET TIMING ON ARRAYSIZE 15

WITH

  FUNCTION slow_function(p_id IN NUMBER) RETURN NUMBER DETERMINISTIC IS

  BEGIN

    DBMS_LOCK.sleep(1);

    RETURN p_id;

  END;

SELECT slow_function(id)

FROM   test

WHERE  ROWNUM <= 10;

/

SLOW_FUNCTION(ID)

-----------------
1
1
1
1
1
1
1
1
1
1

10 rows selected.

Elapsed: 00:00:10.07

SQL>

9. 标量子查询缓冲

前面部分,我们看到行内函数定义对DETERMINISTIC hint优化上的负面影响。 庆幸的是,标量子查询缓冲并不被同样被影响。

SET TIMING ON

WITH

  FUNCTION slow_function(p_id IN NUMBER) RETURN NUMBER DETERMINISTIC IS

  BEGIN

    DBMS_LOCK.sleep(1);

    RETURN p_id;

  END;

SELECT (SELECT slow_function(id) FROM dual)

FROM   test

WHERE  ROWNUM <= 10;

/

(SELECTSLOW_FUNCTION(ID)FROMDUAL)

---------------------------------
1
1
1
1
1
1
1
1
1
1

10 rows selected.

Elapsed: 00:00:01.04

SQL>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: