WHERE 子句中的子查询导致在限定之前进行完整 table 扫描

Subquery in WHERE clause results in full table scan before qualification

我有以下查询

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

当我 运行 这个查询时,执行计划显示它 return 在 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 我希望子查询 return PartID 然后在过滤中使用它。

有什么建议吗?

我试过将 PartID 粘贴到私人临时 Table、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

一些细节:

WOID 是一个 NUMBER(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);

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

定义一个:

create index LkPartProduct_PartID_idx on LkPartProduct(PartID);

您在 WO.WOID 上有一个索引,但它是 WOIDROUTINGID 上的复合索引。您可以通过仅在 WOID:

上创建一个来提高性能
create index WO_WOID_idx on WO(WOID);

我无法解释为什么要这样做

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 毫秒。