我遇到了一个令人费解的情况。查询有一个很好的执行计划。但是当该查询在较大的查询中用作内部查询时,该计划已更改。我试图理解为什么会这样。
这是在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_I4和YFS_SHIPMENT_LINE_H_I6访问表YFS_SHIPMENT_LINE_H;然后将结果连接起来。这个计划似乎很好,查询响应时间很长。
但是当我运行完整查询时,内部查询的访问路径会发生变化,如下所示:
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的最佳方法,为什么它不会继续使用相同的执行计划,即使它是较大查询的一部分?
注意:我不太关心正确的访问路径或使用的索引;因此没有在这里提供所有索引;和数据的基数。我关心的是单独执行时的更改,而不是另一个查询的一部分。
感谢。
- Parag
答案 0 :(得分: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会忽略它,但它可能会增加开销。YFS_SHIPMENT_LINE_H(ORDER_HEADER_KEY, SHIPMENT_KEY)
和YFS_SHIPMENT_LINE_H(ORDER_LINE_KEY, SHIPMENT_KEY)
)。答案 1 :(得分:0)
在第一个查询(不用作子查询)中,基于where
子句中的条件,基表被访问。涉及的两列上的索引用于访问行。
在复杂查询中,您正在进行半连接。优化器正确或错误地认为,首先从shipment
表读取行,阅读shipment_key
并使用shipment_key
中shipment_line
的索引更有效率。 1}}表来检索行以查看它们是否匹配。 where
表上的shipment_line
子句条件现在只是过滤谓词,它们不用于决定从表中检索哪些行。
如果你觉得优化器弄错了(这是可能的,虽然不常用于像这样的相对简单的查询),但要确保统计数据是最新的。这里有关的是每个表的大小,shipment_key
中平均有多少行shipment_line
,以及子查询中where
子句中条件的选择性。请记住,对于外部查询,没有必要完整地计算子查询(很可能Oracle不会完全计算它);对于shipment
表中的每一行,只要找到满足shipment_line
子句的where
表中的匹配行,就会在{{shipment_key
中搜索shipment_line
1}}停止。
如果您真的认为优化程序错了,那么您可以做的一件事就是看看如果您使用提示会发生什么。例如,您可以告诉优化器不要在I2
上使用shipment_line
索引(假装它不存在) - 看看它会提出什么样的计划。
答案 2 :(得分:0)
loan_key上的join强制优化器使用最具选择性的索引,在本例中为YFS_SHIPMENT_LINE_H_I2索引。 Sterling为此查询创建了此索引,它是错误的。丢弃它(或使其不可见)并观察您的查询以获取正确的计划。如果您因为它是Sterling产品的一部分而对删除索引犹豫不决,请使用SQL Plan Management基准。
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