【问题标题】:How does Oracle process stored function calls in SQL?Oracle如何处理SQL中的存储函数调用?
【发布时间】:2010-09-07 08:29:08
【问题描述】:

伙计们。说,我有一个问题:

select t.value, my_stored_function(t.value)
  from my_table t
 where my_stored_function(t.value) = n_Some_Required_Value

我用以下方式重写了它:

select value, func_value
  from (select t.value, my_stored_function(t.value) func_value 
          from my_table t) subquery
 where subquery.func_value = n_Some_Required_Value

让我们将my_stored_function 视为资源消耗。我假设,在第二个查询中,它的调用次数减少了两次,但在此更改后我没有体验到任何显着的性能提升。

所以,我想,我的假设是错误的。那么 Oracle 究竟是如何处理这些函数调用的呢?

【问题讨论】:

    标签: sql oracle performance stored-functions


    【解决方案1】:

    这是一个非常好的问题。

    我首先尝试创建表并插入示例数据(仅五行):

    create table my_table(value number);
    insert into my_table(value) values(1);
    insert into my_table(value) values(2);
    insert into my_table(value) values(3);
    insert into my_table(value) values(4);
    insert into my_table(value) values(5);
    

    我做了一个简单的测试包来测试这个。

    create or replace package my_package is
      g_counter_SELECT PLS_INTEGER := 0; -- counter for SELECT statement
      g_counter_WHERE  PLS_INTEGER := 0; -- counter for WHERE clause
      function my_function(number_in in number, type_in in varchar2) return number;
      procedure reset_counter;
    end;
    /
    

    还有身体……

    create or replace package body my_package is
      function my_function(number_in in number, type_in in varchar2) return number is
      begin
        IF(type_in = 'SELECT') THEN
            g_counter_SELECT := g_counter_SELECT + 1;
        ELSIF(type_in = 'WHERE') THEN
            g_counter_WHERE := g_counter_WHERE + 1;
        END IF;
        return mod(number_in, 2);
      end;
      procedure reset_counter is
      begin
        g_counter_SELECT := 0;
        g_counter_WHERE := 0;
      end;
    end;
    /
    

    现在,我们可以在 Oracle 9i 上运行测试(在 11g 上是相同的结果):

    -- reset counter
    exec my_package.reset_counter();
    
    -- run query
    select t.value, my_package.my_function(t.value, 'SELECT')
      from my_table t
     where my_package.my_function(t.value, 'WHERE') = 1;
    
    -- print result
    exec dbms_output.put_line('Count (SELECT) = ' || my_package.g_counter_SELECT);
    exec dbms_output.put_line('Count (WHERE) = ' || my_package.g_counter_WHERE);
    

    结果是:

    DBMS Output (Session: [1] SCOTT@ORA9i at: 08.09.2010 01:50:04): 
    -----------------------------------------------------------------------
    Count (SELECT) = 3
    Count (WHERE) = 5
    

    这是计划表:

    --------------------------------------------------------------------
    | Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
    --------------------------------------------------------------------
    |   0 | SELECT STATEMENT     |             |       |       |       |
    |*  1 |  TABLE ACCESS FULL   | MY_TABLE    |       |       |       |
    --------------------------------------------------------------------
    

    这意味着该函数(在 WHERE 计算中)为表的每一行调用(在 FULL TABLE SCAN 的情况下)。在 SELECT 语句启动时,只要符合条件 WHERE my_function = 1

    现在...测试您的第二个查询(在 Oracle9i 和 11g 上的结果相同)

    结果是:

    DBMS Output (Session: [1] SCOTT@ORA9i at: 08.09.2010 02:08:04): 
    -----------------------------------------------------------------------
    Count (SELECT) = 8
    Count (WHERE) = 0
    

    解释如下所示(对于 CHOOSE 优化器模式):

    --------------------------------------------------------------------
    | Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
    --------------------------------------------------------------------
    |   0 | SELECT STATEMENT     |             |       |       |       |
    |*  1 |  TABLE ACCESS FULL   | MY_TABLE    |       |       |       |
    --------------------------------------------------------------------
    

    问题是:为什么计数 (SELECT) = 8?

    因为 Oracle 首先运行子查询(在我使用 FULL TABLE SCAN 的情况下,它是 5 行 = 5 在 SELECT 语句中调用 my_function):

    select t.value, my_package.my_function(t.value, 'SELECT') func_value from my_table t
    

    对于这个视图(子查询就像视图)运行 3 次(由于 subquery.func_value = 1 的条件)再次调用函数 my_function。

    个人不建议在WHERE子句中使用函数,但我承认有时这是不可避免的。

    以下是最糟糕的例子:

    select t.value, my_package.my_function(t.value, 'SELECT')
      from my_table t
     where my_package.my_function(t.value, 'WHERE') = my_package.my_function(t.value, 'WHERE')
       and my_package.my_function(t.value, 'WHERE') = my_package.my_function(t.value, 'WHERE')
       and my_package.my_function(t.value, 'WHERE') = my_package.my_function(t.value, 'WHERE')
       and my_package.my_function(t.value, 'WHERE') = my_package.my_function(t.value, 'WHERE')
       and my_package.my_function(t.value, 'WHERE') = my_package.my_function(t.value, 'WHERE');
    

    Oracle 9i 上的结果在哪里

    Count (SELECT) = 5
    Count (WHERE) = 50
    

    在 Oracle 11g 上是

    Count (SELECT) = 5
    Count (WHERE) = 5
    

    在这种情况下,这表明有时函数的使用可能对性能至关重要。在其他情况下(11g)它解决了数据库本身。

    【讨论】:

    • 哈!有趣的是,如果我查询`select t1.value from (select t.value, my_package.my_function(t.value, 'WHERE') fv from my_table t) t1 where t1.fv = 1`(实际上没有列出我在输出字段中的fv 列) - 它保持在五个。我认为 oracle 只是将子查询级别的函数调用结果传播到上层,它确实如此,但以一种非常奇怪的方式。 where t1.fv = 1 不会再次调用函数,但在选定列的列表中列出 t1.fv 会。
    • 是的,最好的解决方案是:select t.value, n_Some_Required_Value from my_table t where my_stored_function(t.value) = n_Some_Required_Value;
    • 问题是,实际上我必须检查my_stored_function 是否不等于零并输出它的实际值,如果不是:))
    • 那又如何(在 WHERE 中使用它而不使用函数):... where case when t.value != 0 then t.value else 0 end;
    • 在这个测试中,您使用了函数的第二个参数来区分这两个调用。因此,这不是对两个调用相同时可能发生的情况的有效测试。
    【解决方案2】:

    在这两种情况下,my_table 中的每一行都会调用一次该函数。在第一种情况下,调用将是where 子句的结果,并且它刚刚找到的值将被返回而无需再次计算。在第二种情况下,所有计算值都将从子查询返回,然后由外部查询的where 子句过滤。

    编辑:根据 Martin 的测试显然不是真的。现在我必须回去找到我多年前所做的测试,让我认为是这种情况,看看我做错了什么。关于联邦调查局的那一点仍然是真实的。我希望。

    在内存使用和优化器使用的确切计划方面可能存在一些细微差别,但我认为两者都不重要。几乎可以肯定,这与函数调用本身的成本无关。

    我认为优化它的唯一方法是使用基于函数的索引。

    【讨论】:

    • 为了使基于函数的索引成为可能,存储的函数必须是确定性的。
    【解决方案3】:

    一个简单的测试:

    create or replace function print_function(v1 number) return number is
    begin
       dbms_output.put_line(v1);
       return v1;
    end;
    /
    
    select print_function(ASCII(dummy)) as test
      from dual
     where chr(print_function(ASCII(dummy))) = dummy;
    

    结果(使用 10g):

          TEST
    ----------
            88
    
    88
    88
    

    结论:函数在SELECT和WHERE子句中分别执行。

    【讨论】:

      【解决方案4】:

      您可以使用 PL/SQL pragma 来影响 oracle 优化查询的方式,请参阅RESTRICT_REFERENCES Pragma

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-06-05
        • 2022-01-18
        • 1970-01-01
        • 1970-01-01
        • 2017-05-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多