Oracle 如何处理 SQL 中的存储函数调用?

发布于 2024-09-17 11:50:00 字数 541 浏览 12 评论 0原文

伙计们。假设我有一个查询:

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实际上是如何处理这些函数调用的呢?

guys. Say, I have a query:

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

I have rewritten it in the following way:

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

Let's think of my_stored_function as of resource-consuming one. I assume, in the second query it is called twice less, but I didn't experience any significant performance increase after this change.

So, I guess, my assumption was wrong. How does Oracle actually process these function calls then?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

旧瑾黎汐 2024-09-24 11:50:00

这是一个非常好的问题。

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

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 计算中)调用该函数(在 WHERE 计算中)全表扫描的情况)。在 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

简单地解释如下(对于选择优化器模式):

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

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

因为 Oracle 首先运行子查询(在我使用全表扫描的情况下,它是 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),它解决数据库本身的问题。

It's a really good question.

I first tried create table and insert sample data (five rows only):

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);

I made a simple test package for testing this.

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;
/

And body...

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;
/

Now, we can run test on Oracle 9i (on 11g are same results):

-- 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);

Result is:

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

Here is plan table:

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

Which means that the function (in WHERE calues) is called for every row of the table (in the case of FULL TABLE SCAN). In the SELECT statement is launched just as many times comply with condition WHERE my_function = 1

Now... test your second query (same results on Oracle9i and 11g)

Result is:

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

Explain plain look like this (for CHOOSE optimizer mode):

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

QUESTION IS: Why Count (SELECT) = 8?

Because Oracle first run subquery (in my case with FULL TABLE SCAN, it's 5 rows = 5 calls my_function in SELECT statement):

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

And than for this view (subquery is like view) run 3 times (due to the condition where subquery.func_value = 1) again call function my_function.

Personally not recommend to use function in the WHERE clause, but I admit that sometimes this is unavoidable.

As the worst possible example of this is illustrated by the following:

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');

Where the result on Oracle 9i is:

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

And on Oracle 11g is:

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

Which in this case shows that sometimes the use of functions may be critical for performance. In other cases (11g) it solves the database itself.

朦胧时间 2024-09-24 11:50:00

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

编辑: 显然不是基于 true马丁的测试。现在我必须回去找到几年前让我认为情况确实如此的测试,看看我做错了什么。关于联邦调查局的那句话仍然是正确的。我希望。

内存使用情况可能存在一些细微的差异,优化器可能使用的确切计划也可能存在一些细微的差异,但我认为两者都不会很重要。几乎可以肯定不会反对函数调用本身的成本。

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

In both cases the function will be called once for every row in my_table. In the first case the call will be as a result of the where clause and the value it's just found will be returned without being calculated again. In the second case all the calculated values will be returned from the subquery and will then be filtered by the outer query's where clause.

Edit: Apparently not true based on Martin's testing. Now I have to go back and find the testing I did years ago that made me think this was the case, and see what I did wrong. The bit about FBIs is still true. I hope.

There may be some minor difference in memory usage and possibly the exact plan used by the optimizer but I wouldn't have thought either would be significant. Almost certainly not against the cost of the function call itself.

The only way I can see to optimize this is with a function based index.

寻找一个思念的角度 2024-09-24 11:50:00

一个简单的测试:

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子句中分别执行。

A simple test:

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;

Results (using 10g):

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

88
88

Conclusion: The function was executed separately in the SELECT and WHERE clauses.

兰花执着 2024-09-24 11:50:00

您可以使用 PL/SQL 编译指示来影响 Oracle 优化查询的方式,请参阅 RESTRICT_REFERENCES 指令

You can use PL/SQL pragmas to affect the way oracle optimizes the query, see RESTRICT_REFERENCES Pragma

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文