【问题标题】:How to inline a variable in PL/SQL?如何在 PL/SQL 中内联变量?
【发布时间】:2011-07-18 06:00:39
【问题描述】:

情况

我的查询执行计划在 Oracle 11.2.0.2.0 中针对大量数据进行中型查询时遇到了一些问题。为了加快速度,我引入了一个范围过滤器,大致如下:

PROCEDURE DO_STUFF(
    org_from VARCHAR2 := NULL,
    org_to   VARCHAR2 := NULL)

  -- [...]
  JOIN organisations org
    ON (cust.org_id = org.id
   AND ((org_from IS NULL) OR (org_from <= org.no))
   AND ((org_to   IS NULL) OR (org_to   >= org.no)))
  -- [...]

如您所见,我想使用可选的组织编号范围来限制organisationsJOIN。客户端代码可以调用DO_STUFF,有(应该很快)或没有(非常慢)限制。

麻烦

问题是,PL/SQL 会为上述org_fromorg_to 参数创建绑定变量,这是我在大多数情况下所期望的:

  -- [...]
  JOIN organisations org
    ON (cust.org_id = org.id
   AND ((:B1 IS NULL) OR (:B1 <= org.no))
   AND ((:B2 IS NULL) OR (:B2 >= org.no)))
  -- [...]

解决方法

仅在这种情况下,当我只内联值时,我测量查询执行计划要好得多,即当 Oracle 执行的查询实际上类似于

  -- [...]
  JOIN organisations org
    ON (cust.org_id = org.id
   AND ((10 IS NULL) OR (10 <= org.no))
   AND ((20 IS NULL) OR (20 >= org.no)))
  -- [...]

“很多”是指快 5-10 倍。请注意,查询很少执行,即每月一次。所以我不需要缓存执行计划。

我的问题

  • 如何在 PL/SQL 中内联值?我知道EXECUTE IMMEDIATE,但我更愿意让PL/SQL 编译我的查询,而不是进行字符串连接。

  • 我只是测量了巧合发生的事情还是我可以假设内联变量确实更好(在这种情况下)?我问的原因是因为我认为绑定变量会迫使 Oracle 设计一个通用执行计划,而内联值将允许分析非常具体的列和索引统计信息。所以我可以想象这不仅仅是巧合。

  • 我错过了什么吗?除了变量内联(注意我也尝试了很多提示,但我不是该领域的专家),也许还有一种完全其他的方法可以实现查询执行计划的改进?

【问题讨论】:

  • 我很确定这是一个巧合。所有查询都是“编译”的,我完全确定我遵循您所询问的“内联变量”。绑定变量是 Oracle 告诉“获取共享池中的值”的方式。
  • @Sathya,我会更新我的问题。 “内联”是指“内联”,即变量不再是变量,而是 SQL 语句的常量
  • 您是否真的获得了与绑定变量和硬编码值一致的不同查询计划?或者您的性能差异可能是由于“慢”查询将一堆数据读取到缓存(数据库、操作系统或 SAN)中,以便“快速”查询从更快的读取中受益?什么版本的甲骨文?假设ORGANIZATIONS 中的NO 列是唯一键是否安全? NO 列的统计信息准确吗?
  • @Justin 是的,计划是一致的。我测量了 1、2、1、2、1、2 等。我还检查了各种绑定值。使用绑定变量,我得到一些全表扫描,而使用硬编码值,计划看起来好多了。版本是 11.2.0.2.0。 NO 独一无二,统计准确。
  • @Justin,以防万一,问题不在organisations 表内。那个只有大约 550 条记录。每个组织在涉及完整查询的各种其他表中都有 10 万多条记录

标签: oracle plsql sql-execution-plan bind-variables


【解决方案1】:

我将此作为评论添加,但也会在这里提供。希望这不是过于简单化,看看详细的回复我可能会误解确切的问题,但无论如何......

您的组织表似乎有定义为数字的列号 (org.no)。在您的硬编码示例中,您使用 numbers 进行比较。

JOIN organisations org
    ON (cust.org_id = org.id
   AND ((10 IS NULL) OR (10 <= org.no))
   AND ((20 IS NULL) OR (20 >= org.no)))

在您的程序中,您传入 varchar2

PROCEDURE DO_STUFF(
    org_from VARCHAR2 := NULL,
    org_to   VARCHAR2 := NULL)

所以要将 varchar2 与数字进行比较,Oracle 必须进行转换,因此这可能会导致完全扫描。

解决方案:更改 proc 以传入数字

【讨论】:

  • 不错的观察,我没有想到这一点。其实是反过来的。内联示例应使用字符串,而不是数字,因为 org.no 的类型为 VARCHAR2
  • @lucas,谢谢。考虑到其他 cmets/解决方案,似乎过于简单。可能不会更改使用绑定 vars 的解释计划,但如果将数字与 varchars 进行比较(反之亦然),实际运行时应该会受到影响。无论如何,希望能有所帮助。
【解决方案2】:

这看起来很像需要自适应游标共享以及 SQLPlan 稳定性。 我认为正在发生的事情是capture_sql_plan_baselines parameter is trueuse_sql_plan_baselines 也是如此。如果情况属实,则发生以下情况:

  1. 第一次启动查询时,它会被解析,它会得到一个新的计划。
  2. 第二次,此计划作为接受的计划存储在 sql_plan_baselines 中。
  3. 此查询的所有后续运行都使用此计划,无论绑定变量是什么。

如果自适应游标共享已经激活,优化器将生成一个新的/更好的计划,将其存储在 sql_plan_baselines 但不能使用它,直到有人接受这个更新的计划作为可接受的替代计划。检查dba_sql_plan_baselines 并查看您的查询是否包含带有accepted = 'NO' and verified = null 的条目 您可以使用dbms_spm.evolve 来改进新计划,如果该计划的性能至少比没有新计划的情况好 1.5 倍,它会自动接受。

我希望这会有所帮助。

【讨论】:

  • 我每天都越来越喜欢您的专业知识!也许我们应该在我们公司雇用你! :-) 我会尽快检查您的建议。感谢您的反馈!
  • @Lukas Eder,我总是乐于接受建议。谢谢:-D
【解决方案3】:

在你的一个 cmets 中你说:

“我还检查了各种绑定值。 使用绑定变量,我得到了一些 FULL 表扫描,而使用硬编码 值,计划看起来好多了。”

有两条路。如果您为参数传入 NULL,那么您将选择所有记录。在这些情况下,全表扫描是检索数据的最有效方式。如果您传入值,那么索引读取可能会更有效,因为您只选择了一小部分信息。

当您使用绑定变量制定查询时,优化器必须做出决定:它应该假定大多数时候您将传递值还是传递空值?难的。所以换个角度看:是只需要选择记录的子集时进行全表扫描,还是需要选择所有记录时进行索引读取效率低?

似乎优化器已将全表扫描视为覆盖所有可能性的最低效率操作。

而当您对值进行硬编码时,优化器会立即知道 10 IS NULL 的计算结果为 FALSE,因此它可以权衡使用索引读取来查找所需子集记录的优点。


那么,该怎么办?正如您所说,此查询每月仅运行一次,我认为只需对业务流程进行少量更改即可拥有单独的查询:一个用于所有组织,一个用于组织子集。


“顺便说一句,删除 :R1 IS NULL 子句 不改变执行计划 很多,这让我和另一个 OR 条件的一侧,:R1

好的,问题是你有一对指定范围的绑定变量。根据值的分布,不同的范围可能适合不同的执行计划。也就是说,这个范围(可能)适合索引范围扫描......

WHERE org.id BETWEEN 10 AND 11

...而这可能更适合全表扫描...

WHERE org.id BETWEEN 10 AND 1199999

这就是绑定变量窥视发挥作用的地方。

(当然取决于值的分布)。

【讨论】:

  • 感谢您的反馈。这也是我的直觉思维。我只是想确定一下。顺便说一句,删除 :R1 IS NULL 子句并没有太大改变执行计划,这让我有了OR 条件的另一边,:R1 &lt;= org.no 其中NULL 无论如何都没有意义,因为org.noNOT NULL
  • 感谢您的更新。我阅读你们的建议越多,我就越得出结论,无论我绑定什么值到查询,都会重复使用相同的计划,就像 EvilTeach 发布的文章中一样。那篇文章还提到了范围谓词。事实上,删除范围谓词并用简单的org.no = :B1 替换它似乎也提供了一个可接受的计划。但是,由于我使用的是 11g 而不是 10g,因此我将仔细检查并验证 ik_zelf 的建议。自 11g 升级以来,Oracle 中添加了许多功能。
【解决方案4】:

由于查询计划实际上始终不同,这意味着优化器的基数估计由于某种原因而关闭。您能否从查询计划中确认优化器期望在使用绑定变量时条件选择性不足?由于您使用的是 11.2,Oracle 应该使用 adaptive cursor sharing,因此它不应该是绑定变量窥视问题(假设您在测试中多次调用具有不同 NO 值的绑定变量的版本。

对好计划的基数估计真的正确吗?我知道您说过 NO 列上的统计数据是准确的,但我会怀疑您的常规统计数据收集过程可能不会更新的杂散直方图。

您始终可以在查询中使用提示来强制使用特定索引(尽管从长期维护的角度来看,使用stored outline or optimizer plan stability 会更好)。这些选项中的任何一个都比使用动态 SQL 更可取。

然而,另一个可以尝试的测试是将 SQL 99 连接语法替换为 Oracle 的旧语法,即

SELECT <<something>>
  FROM <<some other table>> cust,
       organization org
 WHERE cust.org_id = org.id
   AND (    ((org_from IS NULL) OR (org_from <= org.no)) 
        AND ((org_to   IS NULL) OR (org_to   >= org.no)))

这显然不应该改变任何东西,但 SQL 99 语法存在解析器问题,因此需要检查。

【讨论】:

  • 感谢您的广泛反馈!我会尝试你建议的东西,并仔细检查统计数据和直方图。在这种情况下,长期维护并不是什么大问题,因为我们的数据库很少发生变化。但话又说回来,我不是 Oracle 专家,所以也许你指出了一些我完全不知道的事情。
  • 关于基数估计不正确的良好输入。对于每个内联值,我总是得到相同的基数,所以也许我碰巧遇到了与绑定变量相同的问题,只是我很幸运在第一次尝试时就得到了一个“好”的计划。我会继续调查...
  • 我只是尝试避免使用 SQL 99 JOIN 语法,将其替换为交叉连接和谓词。它完全改变了执行计划,但结果是一样的。使用绑定变量我得到了一个糟糕的计划,使用内联值我得到了一个好的计划
【解决方案5】:

闻起来像 Bind Peeking,但我只在 Oracle 10 上,所以我不能声称在 11 中存在同样的问题。

【讨论】:

  • 感谢您的意见。我马上去看看那篇文章!
  • 查看贾斯汀的回答,他还提到了“变量偷看”
  • 您的文章似乎确实描述了我所经历的症状。即使我更改绑定值,来自各种执行计划的基数似乎也没有改变。
  • @lukas - 从绑定变量切换到硬编码的气味似乎使性能问题消失了。我有一个多会话应用程序,有些运行得很快,有些运行得很慢,因为计划是为小 N(全表扫描)生成的,然后应用到大 N。绑定偷看原来是原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-11
  • 1970-01-01
  • 1970-01-01
  • 2012-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多