为啥 PLSQL 比 SQL*Plus 慢

Posted

技术标签:

【中文标题】为啥 PLSQL 比 SQL*Plus 慢【英文标题】:Why is PLSQL slower than SQL*Plus为什么 PLSQL 比 SQL*Plus 慢 【发布时间】:2009-11-05 17:47:38 【问题描述】:

我有几个通过 SQL*PLUS 运行时执行良好的 Oracle 查询。但是,当它们作为 PL/SQL 包的一部分执行时,它们需要更长的时间。

我们的 DBA 发现这些查询通过 PLSQL 需要 10 分钟,通过 SQL*Plus 需要 10 秒。

有没有人指出在哪里寻找错误配置?

客户端 - Windows 2000 服务器 - Linux (Oracle Enterprise)

谢谢

--

分辨率:

我希望我能接受每个人的答案。其中有几个很有帮助。

查询正在转换数据类型。 执行计划不匹配。 (提示解决了这个问题。) DBA 正在查看光标所在的时间 打开而不是查询时间。

【问题讨论】:

可能是隔离级别或自动提交的差异?尝试在每个环境中非常明确地控制这些,看看性能差异是否仍然存在。 你需要一个新的 DBA,对公司来说是新人,而不是 Oracle 的新人 【参考方案1】:

使用 SQL 跟踪查看每种情况下的执行计划。想到的一种可能性(根据经验):包是否将错误类型的值绑定到查询?可能是您在 SQL Plus 中运行:

select * from mytable where id = '1234';

但是在 PL/SQL 中你正在运行:

select * from mytable where id = p_id;

其中 p_id 被定义为一个数字。这将在 ID 列上强制使用 TO_NUMBER 并阻止 Oracle 使用索引。

【讨论】:

查询正在转换数据类型。执行计划不匹配。 (提示解决了这个问题。)DBA 正在查看光标打开的时间而不是查询时间。【参考方案2】:

很可能不是查询运行时间更长,而是在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

或者当触发器触发时。

【讨论】:

【参考方案3】:

我们的 DBA 发现这些查询通过 PLSQL 需要 10 分钟,通过 PL/PSQL 需要 10 秒。

如果 DBA 不想为您解决这个问题,我可以理解,但如果您的 DBA 确实已经看到了这两种情况,并且还没有为您提供两种情况的解释计划,那么他确实不是一个很好的 DBA。

可能没有错误配置,我自己也遇到过 - 所有绑定变量,没有常量,没有提示。直接运行——性能好。把它放在 BEGIN..END 里面 - bam,慢得要命。事实证明,有时查询只是使用 PL/SQL(即 Oracle 9.2)中的不同执行计划。

我的解决方案 - 在 PL/SQL 版本使用与 SQL 相同的计划之前使用提示。

其他可能的问题:

    SQL*Plus 只返回前 100 个或 所以行,然后等你要求更多,但PL/SQL必须处理 他们都没有问。微不足道的问题,但有时会被忽视。 您为 SQL*Plus 使用常量,为 PL/SQL 绑定变量。有时使用常量可以让优化器检查倾斜数据,并且可以使用其他索引。

【讨论】:

【参考方案4】:

您真的在这里比较同类吗?您是在 PL/SQL 中执行原始 SQL 语句(最佳情况)还是使用显式或隐式游标返回值然后处理它们?有很大的不同。

【讨论】:

【参考方案5】:

我们遇到了类似的问题。更新查询在 PL/SQL 块中使用时运行速度非常慢 17 分钟,而在 PL/SQL 之外使用时执行速度非常快(不到 2 秒)。

我们发现 PL/SQL 中使用的执行计划不同。

使用“alter system flush shared_pool”为我们解决了这个问题。这似乎迫使 PL/SQL 重新考虑要使用的执行计划。

【讨论】:

【参考方案6】:

引用和扩展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。

【讨论】:

【参考方案7】:

通过 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),但也要检查执行计划并进行单元测试。

【讨论】:

嗯,大错特错了。插入始终是 SQL。就像删除、更新和选择一样。通过 SQL 引擎处理插入。如果 pl/sql 块中有 INSERT,则 PL/SQL 引擎执行到 SQL 引擎的上下文切换。您需要做的就是阅读 BULK COLLECT 文档。 download.oracle.com/docs/cd/B19306_01/appdev.102/b14261/… "PL/SQL将DML和查询等SQL语句发送给SQL引擎执行,SQL将结果数据返回给PL/SQL。" 感谢斯蒂芬妮的反馈,更新了我的答案。我不知道它们是同一个引擎,但我试图提出的观点是相同的(即在 SQL 引擎之上有一层 PL/SQL 处理可能会导致性能下降)

以上是关于为啥 PLSQL 比 SQL*Plus 慢的主要内容,如果未能解决你的问题,请参考以下文章

windows下, sql plus 远程 连接oracle 不行,但是用客户端, plsql developer远程就可以, 这是为啥啊?

PLsq developer 只能连接ORACLE数据库么

为啥带有'exists'的sql运行速度比使用MySQL的'in'慢

plsql如何执行.sql文件?

Jdbc execute 比工具速度慢

plsql执行sql语句,一行太长,如何分行,便于阅读