如何在PL / SQL中内联变量?

时间:2011-03-18 15:00:25

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

情况

对于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)))
  -- [...]

如您所见,我想使用可选的组织编号范围来限制JOIN organisations。客户端代码可以调用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设计通用执行计划,而内联值则允许分析非常具体的列和索引统计信息。所以我可以想象这不仅仅是巧合。

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

5 个答案:

答案 0 :(得分:7)

在你的一条评论中,你说:

  

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

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

当你使用绑定变量表示查询时,优化器必须做出决定:它是否应该假设大部分时间都传入值或者你将传入空值?难。所以换一种方式看一下:当你只需要选择一个记录子集,或者当你需要选择所有记录时进行索引读取时,进行全表扫描是否效率更低?

似乎优化器已经满足于全表扫描,因为它是覆盖所有可能性的效率最低的操作。

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


那么,该怎么办?正如你所说,这个查询每月只运行一次,我认为只需要对业务流程进行一些小改动就可以有单独的查询:一个用于所有组织,一个用于组织子集。


  

“顺便说一句,删除:R1 IS NULL子句   不会改变执行计划   很多,这让我与另一个   OR条件的一侧,:R1 <=   org.no其中NULL没有意义   无论如何,因为org.no不是NULL“

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

WHERE org.id BETWEEN 10 AND 11

......虽然这可能更适合全表扫描......

WHERE org.id BETWEEN 10 AND 1199999

这就是Bind Variable Peeking发挥作用的地方。

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

答案 1 :(得分:4)

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

良好计划的基数估计是否真的正确?我知道您说NO列的统计信息是准确的,但我会怀疑您的常规统计信息收集过程可能无法更新的杂散直方图。

您总是可以在查询中使用提示来强制使用特定索引(但从长期维护角度来看,使用stored outline or optimizer plan stability会更好)。任何这些选项都优于采用动态SQL。

然而,另一个要尝试的测试是用Oracle的旧语法替换SQL 99连接语法,即

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语法存在解析器问题,因此需要检查。

答案 2 :(得分:3)

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

答案 3 :(得分:3)

这看起来很像需要自适应游标共享,结合SQLPlan稳定性。 我认为正在发生的是capture_sql_plan_baselines parameter is true。对use_sql_plan_baselines也一样。如果是这样,则会发生以下情况:

  1. 第一次启动查询时,它会被解析,它会获得一个新计划。
  2. 第二次,此计划作为已接受的计划存储在sql_plan_baselines中。
  3. 此查询的所有后续运行都使用此计划,无论绑定变量是什么。
  4. 如果自适应游标共享已处于活动状态,优化程序将生成新的/更好的计划,将其存储在sql_plan_baselines中但无法使用它,直到某人接受此新计划作为可接受的替代计划。检查dba_sql_plan_baselines并查看您的查询是否包含accepted = 'NO' and verified = null条目 您可以使用dbms_spm.evolve来制定新计划,如果计划的绩效至少比没有新计划好1.5倍,则会自动接受。

    我希望这会有所帮助。

答案 4 :(得分:2)

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

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

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必须进行转换,因此这可能会导致完整扫描。

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