【问题标题】:Execution Plan of Inner Query different when run as part of a larger query当作为更大查询的一部分运行时,内部查询的执行计划不同
【发布时间】:2017-01-24 11:48:48
【问题描述】:

我遇到了一个令人费解的情况。查询具有良好的执行计划。但是,当该查询被用作更大查询中的内部查询时,该计划发生了变化。我试图理解为什么会这样。

这是在 Oracle 11g 上。我的查询是:

SELECT * FROM YFS_SHIPMENT_H     
WHERE  SHIPMENT_KEY IN 
    (
      SELECT DISTINCT SHIPMENT_KEY 
      FROM YFS_SHIPMENT_LINE_H 
      WHERE  ORDER_HEADER_KEY = '20150113083918815889858'  
      OR ( ORDER_LINE_KEY IN (  '20150113084438815896336') ) 
    );

如您所见,这里有一个内部查询,即:

SELECT DISTINCT SHIPMENT_KEY 
FROM YFS_SHIPMENT_LINE_H 
WHERE  ORDER_HEADER_KEY = '20150113083918815889858'  
OR ( ORDER_LINE_KEY IN (  '20150113084438815896336') ) 

当我只运行内部查询时,我得到的执行计划如下:

PLAN_TABLE_OUTPUT
========================================================================================================
SQL_ID  3v82m4j5tv1k3, child number 0
=====================================
SELECT DISTINCT SHIPMENT_KEY FROM YFS_SHIPMENT_LINE_H WHERE
ORDER_HEADER_KEY = '20150113083918815889858'  OR ( ORDER_LINE_KEY IN (
'20150113084438815896336') )

Plan hash value: 3691773903

========================================================================================================
| Id  | Operation                     | Name                   | Rows  | Bytes | Cost (%CPU)| Time     |
========================================================================================================
|   0 | SELECT STATEMENT              |                        |       |       |    10 (100)|          |
|   1 |  HASH UNIQUE                  |                        |     7 |   525 |    10  (10)| 00:00:01 |
|   2 |   CONCATENATION               |                        |       |       |            |          |
|   3 |    TABLE ACCESS BY INDEX ROWID| YFS_SHIPMENT_LINE_H    |     1 |    75 |     4   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN          | YFS_SHIPMENT_LINE_H_I4 |     1 |       |     3   (0)| 00:00:01 |
|*  5 |    TABLE ACCESS BY INDEX ROWID| YFS_SHIPMENT_LINE_H    |     6 |   450 |     5   (0)| 00:00:01 |
|*  6 |     INDEX RANGE SCAN          | YFS_SHIPMENT_LINE_H_I6 |     6 |       |     3   (0)| 00:00:01 |
========================================================================================================

Predicate Information (identified by operation id):
===================================================

   4 = access("ORDER_LINE_KEY"='20150113084438815896336')
   5 = filter(LNNVL("ORDER_LINE_KEY"='20150113084438815896336'))
   6 = access("ORDER_HEADER_KEY"='20150113083918815889858')

执行计划显示访问表YFS_SHIPMENT_LINE_H,有两个索引YFS_SHIPMENT_LINE_H_I4和YFS_SHIPMENT_LINE_H_I6;然后将结果连接起来。这个计划看起来不错,查询响应时间也很好。

但是当我运行完整查询时,内部查询的访问路径会发生如下变化:

PLAN_TABLE_OUTPUT
=======================================================================================================
SQL_ID  dk1bp8p9g3vzx, child number 0
=====================================
SELECT * FROM YFS_SHIPMENT_H WHERE SHIPMENT_KEY IN ( SELECT DISTINCT
SHIPMENT_KEY FROM YFS_SHIPMENT_LINE_H WHERE ORDER_HEADER_KEY =
'20150113083918815889858' OR ( ORDER_LINE_KEY IN (
'20150113084438815896336') ) )

Plan hash value: 3651083773

=======================================================================================================
| Id  | Operation                    | Name                   | Rows  | Bytes | Cost (%CPU)| Time     |
=======================================================================================================
|   0 | SELECT STATEMENT             |                        |       |       | 12593 (100)|          |
|   1 |  NESTED LOOPS                |                        |       |       |            |          |
|   2 |   NESTED LOOPS               |                        |     7 |  6384 | 12593   (1)| 00:02:32 |
|   3 |    SORT UNIQUE               |                        |     7 |   525 | 12587   (1)| 00:02:32 |
|*  4 |     INDEX FAST FULL SCAN     | YFS_SHIPMENT_LINE_H_I2 |     7 |   525 | 12587   (1)| 00:02:32 |
|*  5 |    INDEX UNIQUE SCAN         | YFS_SHIPMENT_H_PK      |     1 |       |     1   (0)| 00:00:01 |
|   6 |   TABLE ACCESS BY INDEX ROWID| YFS_SHIPMENT_H         |     1 |   837 |     2   (0)| 00:00:01 |
=======================================================================================================

Predicate Information (identified by operation id):
===================================================

   4 = filter(("ORDER_HEADER_KEY"='20150113083918815889858' OR
              "ORDER_LINE_KEY"='20150113084438815896336'))
   5 = access("SHIPMENT_KEY"="SHIPMENT_KEY")

请注意,现在正在使用不同的索引 (YFS_SHIPMENT_LINE_H_I2) 访问 YFS_SHIPMENT_LINE_H。事实证明,这不是一个很好的索引,查询响应时间也会受到影响。

我的问题是:当内部查询执行计划作为更大查询的一部分运行时,为什么会发生变化?一旦优化器找到了访问 YFS_SHIPMENT_LINE_H 的最佳方式,为什么即使它是更大查询的一部分,它也不会继续使用相同的执行计划?

注意:我不太关心正确的访问路径或要使用的索引;因此这里没有给出表上的所有索引;和数据的基数。我担心的是单独执行而不是作为另一个查询的一部分执行的更改。

谢谢。

-- 段落

【问题讨论】:

    标签: oracle relational-database sql-execution-plan sqlperformance inner-query


    【解决方案1】:

    我不确定 Oracle 优化器为何决定更改执行路径。但是,我认为这是编写查询的更好方法:

    SELECT s.*
    FROM YFS_SHIPMENT_H  s   
    WHERE s.SHIPMENT_KEY IN (SELECT sl.SHIPMENT_KEY 
                             FROM YFS_SHIPMENT_LINE_H sl
                             WHERE sl.ORDER_HEADER_KEY = '20150113083918815889858' 
                            ) OR 
          s.SHIPMENT_KEY IN (SELECT sl.SHIPMENT_KEY 
                             FROM YFS_SHIPMENT_LINE_H sl
                             WHERE sl.ORDER_LINE_KEY IN ('20150113084438815896336')
                            );
    

    注意事项:

    • IN 的子查询中不需要有SELECT DISTINCT。我很确定 Oracle 会忽略它,但它可能会增加开销。
    • 将逻辑拆分为两个查询使 Oracle 更有可能使用索引进行查询(最好的索引位于 YFS_SHIPMENT_LINE_H(ORDER_HEADER_KEY, SHIPMENT_KEY)YFS_SHIPMENT_LINE_H(ORDER_LINE_KEY, SHIPMENT_KEY))。

    【讨论】:

    • 你是对的,查询确实可以用不同的方式完成。这个恰好在我无法更改的代码中。 ......此外,您提出了一个很好的观点,即不必将distinct 用作in 子句的一部分。
    【解决方案2】:

    在第一个查询(不用作子查询)中,根据where 子句中的条件访问基表。所涉及的两列上的索引用于访问行。

    在复杂查询中,您正在执行半联接。优化器,无论对错,决定先从shipment 表中读取行,然后读取shipment_key,然后使用shipment_line 表中shipment_key 上的索引来检索行会更有效。看看他们是否匹配。 shipment_line 表上的 where 子句条件现在只是过滤谓词,它们不用于决定从表中检索哪些行。

    如果您觉得优化器出错了(这是可能的,尽管对于像这样相对简单的查询并不常见),请确保统计信息是最新的。这里相关的是每个表的大小,在shipment_line 中平均有多少行具有相同的shipment_key,以及子查询中where 子句中条件的选择性。请记住,对于外部查询,不需要完整计算子查询(很可能 Oracle 不会完整计算它);对于shipment 表中的每一行,只要在shipment_line 表中找到满足where 子句的匹配行,就会停止在shipment_line 中搜索该shipment_key

    如果你真的认为优化器弄错了,你可以做的一件事是看看如果你使用提示会发生什么。例如,您可以告诉优化器不要在shipment_line 上使用I2 索引(假装它不存在)——看看它会提出什么计划。

    【讨论】:

    • 您提到在复杂查询中,oracle 是先从shipment 表中读取行,然后使用shipment_key 读取shipment line 表。但是看执行计划,感觉oracle是先读取shipment line(对yfs_shipment_line_h_i2使用fast full scan)。你能详细说明一下吗?谢谢。
    【解决方案3】:

    shipping_key 上的连接强制优化器使用最具选择性的索引,在本例中为 YFS_SHIPMENT_LINE_H_I2 索引。 Sterling 为此查询创建了此索引,但它是错误的。删除它(或使其不可见)并观察您的查询选择正确的计划。如果您因为索引是 Sterling 产品的一部分而不愿删除索引,请使用 SQL 计划管理基线。

    YFS_SHIPMENT_LINE_H_I2 SHIPMENT_KEY 1 YFS_SHIPMENT_LINE_H_I2 ORDER_HEADER_KEY 2 YFS_SHIPMENT_LINE_H_I2 ORDER_RELEASE_KEY 3 YFS_SHIPMENT_LINE_H_I2 ORDER_LINE_KEY 4 YFS_SHIPMENT_LINE_H_I2 REQUESTED_TAG_NUMBER 5 个

    【讨论】:

    • 您能否简单解释一下您回复底部的文本块是什么意思?
    • 为什么你认为这个索引是“错误的”?另外,您谈到了“连接”——您意识到查询不会进行连接,对吧?
    • 彼得,谢谢。 Steve/Mathguy,Peter 根据拨打电话的产品知识进行了回答,而不仅限于问题中提供的信息。 Steve,Peter 最后添加的文本块是索引 YFS_SHIPMENT_LINE_H_I2 使用的列。我猜它是以(索引名称,列名称,列位置)开头的表格形式,但不知何故失去了它的格式。
    猜你喜欢
    • 2020-01-09
    • 2020-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-19
    • 1970-01-01
    • 2022-01-26
    • 1970-01-01
    相关资源
    最近更新 更多