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

Luk*_*der 9 oracle plsql bind-variables sql-execution-plan

情况

对于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)))
  -- [...]
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,我想限制JOINorganisations使用数量组织的可选范围.客户端代码可以调用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)))
  -- [...]
Run Code Online (Sandbox Code Playgroud)

解决方法

只有在这种情况下,当我只是内联值时,我测量查询执行计划要好得多,即当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)))
  -- [...]
Run Code Online (Sandbox Code Playgroud)

"很多",我的意思是快了5-10倍.请注意,查询很少执行,即每月执行一次.所以我不需要缓存执行计划.

我的问题

  • 如何在PL/SQL中内联值?我知道EXECUTE IMMEDIATE,但我更喜欢PL/SQL编译我的查询,而不是字符串连接.

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

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

APC*_*APC 7

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

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

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

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

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

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


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


"顺便说一下,删除:R1 IS NULL子句不会更改执行计划,这使我处于OR条件的另一端,:R1 <= org.no其中NULL无论如何都没有意义,如组织.不是非空"

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

WHERE org.id BETWEEN 10 AND 11
Run Code Online (Sandbox Code Playgroud)

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

WHERE org.id BETWEEN 10 AND 1199999
Run Code Online (Sandbox Code Playgroud)

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

(当然,取决于价值分布).