为什么PLSQL比SQL*Plus慢

6
我有几个Oracle查询,在SQL*PLUS中运行良好。但是,当它们作为PL/SQL包的一部分执行时,花费的时间要长得多。
我们的DBA观察到这些查询在PLSQL中需要10分钟,而在SQL*Plus中只需要10秒钟。
有人知道应该从哪里查找错误配置吗?
客户端 - Windows 2000 服务器 - Linux(Oracle Enterprise)
谢谢
--
解决方案:
真希望我能接受每个人的答案。其中几个非常有帮助。
- 查询正在转换数据类型。 - 执行计划不匹配。(提示修复了这个问题。) - DBA查看的是游标打开的时间,而不是查询时间。

也许是隔离级别或自动提交的差异?尝试在每个环境中非常明确地控制它们,看看性能差异是否仍然存在。 - Alex Martelli
2
你需要一位新的数据库管理员,他可能是公司新来的,但不应该是 Oracle 新手。 - Stephanie Page
7个回答

9
使用SQL跟踪来查看每种情况下的执行计划。从经验上来看,可能有一种可能性:包绑定了错误类型的值到查询中。可能是在SQL Plus中运行以下命令:
select * from mytable where id = '1234';

但在PL/SQL中,您正在运行:

select * from mytable where id = p_id;

如果定义 p_id 为数字,则会强制对 ID 列进行 TO_NUMBER 操作,这将阻止 Oracle 使用索引。


1
查询正在转换数据类型。 执行计划不匹配。(提示修复了这个问题。) DBA查看的是光标打开的时间,而不是查询时间。 - Brad Bruce

3

很可能,运行时间较长的不是查询语句,而是处理它们在PL/SQL中的开销。

当您在PL/SQL脚本中处理查询结果时,会发生上下文切换。这需要在Oracle进程之间传递大量数据,速度相当慢。

像这样的代码:

DECLARE
        cnt INTEGER := 0;
        CURSOR  cr_main IS
        SELECT  1 AS id
        FROM    dual
        CONNECT BY
                level <= 1000000;
BEGIN
        FOR res IN cr_main
        LOOP
                cnt := cnt + res.id;
        END LOOP;
        DBMS_OUTPUT.put_line(cnt);
END;

在我的机器上运行超过3秒,而这个:
SELECT  SUM(1)
FROM    dual
CONNECT BY
        level <= 1000000

仅需要 0.5 秒即可完成。

当你像这样从 SQL 调用 PL/SQL 时,也会发生上下文切换:

SELECT  plsql_function(column)
FROM    mytable

当触发器触发时。


2
我们的数据库管理员(DBA)通过PLSQL执行这些查询需要10分钟,而通过PL/PSQL只需要10秒钟。如果你的DBA真的看到了这两种情况并且还没有为两种情况提供解释计划,那么他真的不是一个很好的DBA,我可以理解如果DBA不想为您解决此问题。可能没有配置错误,我自己也遇到过这种情况 - 所有绑定变量,没有常量,没有提示。直接运行 - 性能良好。放在BEGIN..END中 - 超级慢。结果有时候查询仅仅是从PL/SQL中使用了不同的执行计划(这是Oracle 9.2)。我的解决方案是使用提示,直到PL/SQL版本使用与SQL相同的计划。其他可能的问题:1.SQL*Plus仅返回前100个或更少的行,然后等待您请求更多,但PL/SQL必须在未经请求的情况下处理它们。这是微不足道的问题,但有时会被忽视。2.您在SQL*Plus中使用常量,在PL/SQL中使用绑定变量。有时使用常量允许优化器检查数据是否倾斜,并且它可能使用其他索引。

1
你是否真正在进行类似比较?你是在 PL/SQL 中执行原始 SQL 语句(最佳情况),还是使用显式或隐式游标返回值,然后处理它们?这有很大的区别。

1
我们遇到了类似的问题。在 PL/SQL 块中运行更新查询非常缓慢,需要 17 分钟,但在 PL/SQL 外部执行时非常快(不到 2 秒)。
我们发现,在 PL/SQL 中使用的执行计划是不同的。
使用 "alter system flush shared_pool" 解决了我们的问题。它似乎强制 PL/SQL 重新考虑使用的执行计划。

0

引用并扩展Quassnoi的话:

很可能,运行时间更长的不是查询本身,而是在PL/SQL中处理它们所需的开销。

当您在PL/SQL脚本中处理查询结果时,会发生上下文切换。这需要在Oracle进程之间传递大量数据,速度相当慢。

就像这段代码:

DECLARE
        cnt INTEGER := 0;
        CURSOR  cr_main IS
        SELECT  1 AS id
        FROM    dual
        CONNECT BY
                level <= 1000000;
BEGIN
        FOR res IN cr_main
        LOOP
                cnt := cnt + res.id;
        END LOOP;
        DBMS_OUTPUT.put_line(cnt);
END;

在我的电脑上运行超过3秒,而这个:

SELECT  SUM(1)
FROM    dual
CONNECT BY
        level <= 1000000

仅需0.5秒即可完成。

当您从SQL调用PL/SQL时,也会发生上下文切换,如下所示:

SELECT  plsql_function(column)
FROM    mytable
or when a trigger fires.

解决上下文切换问题的一种方法是使用BULK COLLECT。如果您正在收集大量行,则使用BULK COLLECT INTO某种类型的集合可以显着加速PL / SQL语句中的SQL。

0

通过SQLPlus发出的DML(例如SELECT,UPDATE,DELETE)直接发送到Oracle的SQL引擎,而在PLSQL过程中的DML首先由PL/SQL处理(例如进行变量绑定),然后发送到SQL引擎。

在大多数情况下,PL/SQL中的相同语句与SQL执行相同,并且两种方式通常会产生相同的执行计划。根据我的经验(通常在需要变量绑定时),它可能导致非常不同的性能。我曾经看到过在SQL Plus中发出的SELECT只需几分之一秒,而通过PL/SQL发出的SELECT却需要1-2分钟。

我建议您调整语句,使其在PL/SQL中与在SQL中一样有效。正确绑定变量(使用FORALL和BULK COLLECT)是重点,同时还要检查执行计划并进行单元测试。


1
嗯,完全错误。在SQL中,总是使用Insert。Delete、Update和Select也是如此。Insert通过SQL引擎进行处理。如果在PL/SQL块中有INSERT,则PL/SQL引擎会执行上下文切换到SQL引擎。你所需要做的就是阅读BULK COLLECT文档。http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14261/tuning.htm#i48876 “PL/SQL将诸如DML和查询之类的SQL语句发送到SQL引擎以进行执行,并且SQL将结果数据返回给PL/SQL。” - Stephanie Page
谢谢反馈,Stephanie。我已经更新了我的答案。我不知道它们是同一个引擎,但我试图表达的意思是一样的(即,在SQL引擎之上有一层PL / SQL处理,可能会导致性能下降)。 - darreljnz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接