在 PL/SQL 中使用参数或常量时的性能差异

Posted

技术标签:

【中文标题】在 PL/SQL 中使用参数或常量时的性能差异【英文标题】:Performance differents when using parameter or constants in PL/SQL 【发布时间】:2018-09-20 12:57:06 【问题描述】:

我遇到了性能问题。

第一个 PL/SQL(大多数时间永远不会结束,操作系统数据库进程总是超过 90%):

DECLARE 
  myId nvarchar2(10) := '0;WF21izb0';
BEGIN
  insert into MY_TABLE (select * from MY_VIEW where ID = myId);
END;

第二次PL/SQL(以50s成功结束):

BEGIN
  insert into MY_TABLE (select * from MY_VIEW where ID = '0;WF21izb0');
END;

select count(*) from MY_VIEW 

也是一个不结束的调用,这个视图后面有很多表连接。

select count(*) from MY_VIEW where ID = '0;WF21izb0'

以 50 秒结束,count=60000。

有人能解释一下为什么我的第一个 PL/SQL 在 50 多岁之后还没有完成吗?使用静态字符串和声明参数有什么区别?

【问题讨论】:

我建议您跟踪它,然后分析跟踪文件。您可能会遇到数十人说执行计划不同,但他们只是受过高度教育的猜测。如果您养成正确查看的习惯(trace + profile),那么您将能够更快地解决问题。 视图下的表的统计信息是否是最新的? SELECT LAST_ANALYZED, NUM_ROWS FROM DBA_TABLES WHERE TABLE_NAME = 'YOUR_TABLE_NAME_HERE',并将 NUM_ROWSSELECT COUNT(*) FROM YOUR_TABLE_NAME_HERE 进行比较。如果 LAST_ANALYZED 为 NULL 或过期,或者 NUM_ROWS 与实际行数有显着差异,请使用 DBMS_STATS.GATHER_TABLE_STATS 分析表。 我不确定@jeff6times7 是否会将其视为一个受过高度教育的猜测,但似乎(1)您的视图投射了大量的行,并且(2)存在巨大的偏差ID 的卷。给定 ID 的文字值,优化器可以提出一个自定义计划来获取这些记录。但是给定一个盲变量,它使用它已经拥有的任何计划:该计划可能会针对 ID 的值进行优化,它返回两行;这可能并不明显,但对于返回 60000 行的 ID 而言,该计划可能是毁灭性的。 您可以询问您的 DBA,cursor_sharing 参数是否等于 Exact ..? 【参考方案1】:

在准备查询执行计划时,它归结为数据库引擎对您的数据和查询的了解。

当文字被放置在您的查询中时,它就是您的查询的一部分,因此对于负责准备计划的引擎来说是已知的。它可以考虑该字面值并决定合适的执行计划,例如基于数据库数据统计(例如,该值很少见)。

当您使用 PL/SQL 变量时,为其确定计划的实际查询是不同的。是这样的:

insert into MY_TABLE (select * from MY_VIEW where ID = :param)

如您所见,数据库引擎现在没有关于执行查询时将使用的值的信息。因此,对于这种情况,最好的计划是准备一些东西,这对于大多数可能的值来说平均是好的(即查看数据库中的哪些值最常匹配这个地方,即普遍存在的值)。

如果您的数据不平衡,并且您的数据中'0;WF21izb0' 值很少(甚至不存在),则可以使用选择性索引来缩小需要处理的范围,在关键部分相对较快的执行计划。然而,这个计划会适得其反,当你使用一个到处都是的值时——使用索引会适得其反。对于这种情况,更好的计划可能是全表扫描。可能是同一个,在执行select count(*) from MY_VIEW时使用。

如果您遇到不预先知道过滤值的情况,则必须分析视图代码,并尝试对其进行调整,以便它也可以有效地用于不太“选择性”的值。您可以尝试对查询应用一些优化器提示。您也可以放弃使用视图,并使用表格函数试试运气,您可以将过滤谓词推送到查询的确切位置,在那里它们可以最有效地使用。

编辑: 总而言之,请遵循问题 cmets 的建议,并检查您的执行计划和执行配置文件数据。你应该能够找到罪魁祸首。从那里可能并不明显,解决方案是什么,但您仍然比我们更了解您的数据和关系。

【讨论】:

【参考方案2】:

我正在检查一些痕迹,但在阅读了 APC 的评论和 Hilarion 的答案后,我最终得到了这个解决方案:

declare sql_stmt VARCHAR2(200); id VARCHAR2(10) := '0;WF21izb0'; BEGIN sql_stmt := 'insert into MY_TABLE (select * from MY_VIEW where ID = :1)'; EXECUTE IMMEDIATE sql_stmt using id; END;

这是在 50 年代完成的,id 现在可以是函数/过程参数。 感谢 cmets。

【讨论】:

以上是关于在 PL/SQL 中使用参数或常量时的性能差异的主要内容,如果未能解决你的问题,请参考以下文章

在哪里保存 PL/SQL 常量?

转换为数字时的 PL/SQL 数字或值错误

where 条件中的可选参数 - 如何提高性能 - PL/SQL

在 PL/SQL 中使用变量和常量的困惑

使用 jOOQ 执行 PL/SQL 函数时的 Java 空指针

oracle pl/sql 简介