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
上有一个索引,但它是 WOID
和 ROUTINGID
上的复合索引。您可以通过仅在 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 毫秒。
我有以下查询
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
中获取PartIDSELECT 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
上有一个索引,但它是 WOID
和 ROUTINGID
上的复合索引。您可以通过仅在 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 毫秒。