9

情况

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

如您所见,我想限制使用可选的组织编号范围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)))
  -- [...]

解决方法

只有在这种情况下,当我只内联值时,我测量查询执行计划要好得多,即当 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 设计一个通用的执行计划,而内联值将允许分析非常具体的列和索引统计信息。所以我可以想象这不仅仅是巧合。

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

4

5 回答 5

7

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

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

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

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

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

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


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


“顺便说一句,删除 :R1 IS NULL 子句并没有太大改变执行计划,这让我有了 OR 条件的另一面, :R1 <= org.no 其中 NULL 无论如何都没有意义,因为 org.不不为空”

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

WHERE org.id BETWEEN 10 AND 11

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

WHERE org.id BETWEEN 10 AND 1199999

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

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

于 2011-03-18T15:56:22.890 回答
4

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

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

您始终可以在查询中使用提示来强制使用特定索引(尽管从长期维护的角度来看,使用存储的大纲或优化器计划稳定性会更好)。这些选项中的任何一个都比使用动态 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 语法存在解析器问题,因此需要检查。

于 2011-03-18T15:54:36.023 回答
3

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

于 2011-03-18T15:45:07.807 回答
3

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

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

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

我希望这有帮助。

于 2011-03-18T16:27:11.657 回答
2

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

似乎您的组织表具有定义为数字的列号 (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 与 number 进行比较,Oracle 必须进行转换,因此这可能会导致完全扫描。

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

于 2011-03-18T18:07:48.943 回答