0

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

这是在 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 的最佳方式,为什么即使它是更大查询的一部分,它也不会继续使用相同的执行计划?

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

谢谢。

-- 段落

4

3 回答 3

0

我不确定 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')
                        );

笔记:

  • 不需要SELECT DISTINCT在子查询中为IN. 我很确定 Oracle 会忽略它,但它可能会增加开销。
  • 将逻辑拆分为两个查询使得 Oracle 更有可能使用索引进行查询(最好的索引是 onYFS_SHIPMENT_LINE_H(ORDER_HEADER_KEY, SHIPMENT_KEY)YFS_SHIPMENT_LINE_H(ORDER_LINE_KEY, SHIPMENT_KEY))。
于 2017-01-24T11:55:08.110 回答
0

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

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

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

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

于 2017-01-24T13:22:48.327 回答
0

ship_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

于 2017-01-24T16:02:08.530 回答