【问题标题】:Subquery in WHERE clause results in full table scan before qualificationWHERE 子句中的子查询导致在限定之前进行全表扫描
【发布时间】:2021-06-14 04:31:10
【问题描述】:

我有以下查询

SELECT LkPartProduct.PartID, LkPartProduct.ProductID
FROM LkPartProduct
WHERE LkPartProduct.PartID IN (SELECT WO.PartID FROM WO WHERE WO.WOID = 310000000001549881)
AND LkPartProduct.PartIsRetired = 0
AND LkPartProduct.ProductIsRetired = 0

当我运行此查询时,执行计划显示它在执行合并连接之前返回 LkPartProduct(视图)中的每条记录

见:

Id Operation Name Rows Bytes Cost
0 SELECT STATEMENT 33 1881 245M
1 MERGE JOIN 33 1881 245M
2 VIEW 7309K 271M 245M
3 WINDOW SORT PUSHED RANK 7309K 118G 245M
4 FILTER

完整的计划可以在这里找到https://pastebin.com/raw/sCRBhZHS

如果我更改该查询以在不查找的情况下过滤 PartID,则该计划更明智。

SELECT LkPartProduct.PartID, LkPartProduct.ProductID
FROM LkPartProduct
WHERE LkPartProduct.PartID IN (310101554)
AND LkPartProduct.PartIsRetired = 0
AND LkPartProduct.ProductIsRetired = 0
Id Operation Name Rows Bytes Cost
0 SELECT STATEMENT 33 1287 1212
1 VIEW 33 1287 1212
2 WINDOW SORT PUSHED RANK 33 559K 1212
3 FILTER

完整计划在这里https://pastebin.com/raw/fc08r1L1

我知道它是将苹果与橙子进行比较,但 MSSQL 中的相同查询可以正常工作,但在 Oracle 中,它似乎总是在如何最好地查询数据方面做出错误的决定。 Logicall 我希望子查询返回 PartID,然后在过滤中使用它。

有什么建议吗?

我尝试将 PartID 粘贴到私有临时表、CTE、JOIN 中,但没有任何乐趣。

根据要求,这里是视图

https://pastebin.com/raw/3n2qqV0Z

如果我运行以下命令只是从 WO 获取 PartID

SELECT WO.PARTID FROM WO WHERE WO.WOID = 310000000001549881

解释计划如下

Id Operation Name E-Rows E-Bytes Cost
0 SELECT STATEMENT 1 18 3
1 TABLE ACCESS BY INDEX ROWID BATCHED WO 1 18 3
2 INDEX RANGE SCAN IM_WOID_ROUTINGID 1 2

如你所料,运行下面的速度很快

SELECT LkPartProduct.PartID, LkPartProduct.ProductID
FROM LkPartProduct
WHERE LkPartProduct.PartID IN (select /*+ PRECOMPUTE_SUBQUERY */ WO.PartID FROM WO WHERE WO.WOID = 310000000001549881)

在这里解释一下

https://pastebin.com/raw/MwtinW3e

一些细节:

WOIDNUMBER(19)

我有以下索引:

CREATE INDEX I_CID ON WO(CUSTOMERID);
CREATE INDEX I_RFJSID ON WO(RFJOBSTATUSID);
CREATE INDEX I_WO_PARTID ON WO(PARTID);
CREATE INDEX I_WO_RUNNO ON WO(RUNNO, WOID, WONUMBER, PARTID);
CREATE INDEX I_WOREF ON WO(WOREFID);
CREATE INDEX I_WOROUTINGID ON WO(ROUTINGID);
CREATE INDEX IM_WOID_ROUTINGID ON WO(WOID, ROUTINGID);

【问题讨论】:

  • 子查询是否应该准确返回 1 行?如果将IN 替换为= 会发生什么?
  • 始终使用限定名称以确保该字段来自您需要的表。可能过滤表中有同名的列
  • @astentx 我已经更新了,所以它在问题中是合格的,那是我在打字时很懒。
  • 你能发布视图的定义吗?我的猜测是视图定义中有一些东西阻止了 Oracle 推送谓词。
  • 您能否发布完整的计划(包括访问和过滤谓词以及对象名称)?这两个计划的第一步都是过滤器,但一个过滤器可能会过滤掉更多信息,原因尚不清楚。

标签: oracle subquery sql-execution-plan


【解决方案1】:

您在LkPartProduct.PartID 上没有索引,因此对其进行任何查找都需要进行全表扫描。

定义一个:

create index LkPartProduct_PartID_idx on LkPartProduct(PartID);

您在WO.WOID 上有一个索引,但它是WOIDROUTINGID 上的复合索引。您可以通过在 WOID 上创建一个来稍微提高性能:

create index WO_WOID_idx on WO(WOID);

【讨论】:

  • LkPartProduct 是一个视图(定义发布在主要问题中),你不能有索引。
  • 如果你指的是 LkPartProductInner,我刚刚在其中添加了一个,之前我只有 CREATE INDEX SFOL.IUM_PARTID_PRODUCTID_CUSTOMERID_CUSTOMERGROUPID_MACHINESTAGEID_PARTISRETIRED_PRODUCTISRETIRED ON SFOL.LKPARTPRODUCTINNER (PRODUCTID, PARTID, CUSTOMERGROUPID, CUSTOMERID, MACHINESTAGEID, PARTISRETIRED, PRODUCTISRETIRED)
  • @BrandonBillingham 问题应该是独立的:请删除视图的链接并将视图定义放在您的问题中。
  • 视图太大,无法放入问题
【解决方案2】:

我无法解释为什么这样做

SELECT LkPartProduct.PartID, LkPartProduct.ProductID
FROM LkPartProduct,
LATERAL (SELECT WO.PartID FROM WO WHERE WO.WOID = 310000000001549881 AND LkPartProduct.PartID = WO.PartID)
WHERE LkPartProduct.PartIsRetired = 0
AND LkPartProduct.ProductIsRetired = 0

效果很好,但是现在我已经运行了上面的代码,这几乎就像 Oracle 已经弄清楚了,下面的代码同样快,执行计划完全相同。

SELECT ProductID,
       PartID
FROM LkPartProduct
WHERE LkPartProduct.PartID IN (SELECT PartID FROM WO WHERE WOID = 310000000001549881)
AND LkPartProduct.PartIsRetired = 0
AND LkPartProduct.ProductIsRetired = 0

然后我按照@Bohemian 的建议添加了索引,这将性能从 876 毫秒提高到了 542 毫秒。

【讨论】:

  • 可能是因为您进一步限制了PartIsRetiredProductIsRetired(它们存在于LKPARTPRODUCTINNER 的索引中),因此索引跳过扫描的成本低于没有此过滤器的情况。
  • 我在最初的问题中错过了它们,但它们出现在我的测试中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-10
  • 1970-01-01
  • 2011-11-01
  • 2013-10-09
相关资源
最近更新 更多