重写查询替换不在
Posted
技术标签:
【中文标题】重写查询替换不在【英文标题】:rewrite query replacing not in 【发布时间】:2018-11-20 07:42:44 【问题描述】:我想以删除NOT IN
的方式重写以下查询,这可能吗?
select * from TRX_T TT, TRX_SUB TS
where TT.CODE=TS.CODE
and TT.SUBID= TS.ID
and TS.VALUE=1
and TS.CODE=1
AND TS.ID=17
AND TT.STATUS NOT IN('T','R','C')
如果状态是IN
,我会使用union all
我想重写的原因是因为下面的 oracle 推荐。
谓词 "TT"."STATUS"'C' 在执行的第 5 行使用 plan 包含索引列“STATUS”上的表达式。这 表达式阻止优化器有效地使用索引 表 TT。
不同值的数量
T 264
C 5489709
D 2987
J 924
L 529430
R 39382
S 5449
TRX_T
上的索引是这样的:(CODE,SUBID,TYPE,STATUS,NO_SL)
【问题讨论】:
您是否发现查询的性能问题...有理由相信它应该能够运行得比 Oracle 推荐的更快吗?只有当它提供的替代方案(TRX_T 表上的索引访问)可以提高性能时,该建议才有意义——它很可能不会。 解释计划是怎么说的?它使用索引吗?毕竟查询很简单。由于TS.ID = 17
,您选择了一个或零个trx_sub
行。这将为您提供 ID 为 17 的一行,但如果 code
或 value
不等于 1,则将其关闭。对于这条trx_sub
记录,您将获得相关的trx_t
行,并且您在相关列code
和subid
上有一个索引。看起来不错。 trx_t
表中有多少行,您的查询通常检索多少行?
@ThorstenKettner 都在 600 万左右,解释计划显示所有列的读取索引
所以trx_t
中有六百万行。您的查询检索到其中的多少?你说索引正在被使用,所以它显然只是一小部分。那么问题出在哪里? Oracle 似乎认为AND TT.STATUS IN('D','J','L','S')
和(CODE,SUBID,STATUS)
上的索引会更可取,但是这样可以获得查询多快?我预计速度不会有太大变化。
附注:逗号分隔的连接在 1992 年的 SQL 标准中变得多余。Oracle 花了 9 年时间才采用它,但现在仍然是 17 年前。请使用正确的连接 (from trx_t tt inner join trx_sub ts on ...
)。
【参考方案1】:
如果您想调整查询,您需要了解您的数据模型和数据。优化器说它不能有效地使用TRX_T
上的索引。让我们看看那个复合索引:
CODE
:用于连接条件
SUBID
:用于连接条件
TYPE
:没用过
STATUS
: 可以用作过滤器吗?
NO_SL
:没用过
您的查询使用了五个索引列中的三个。但是因为您在STATUS
上有一个 NOT IN 表达式,所以优化器不会使用索引来评估过滤器。因此,它读取TRX_T
中与TRX_SUB
中的记录匹配的每条记录,并评估表上的过滤器。
也许如果您将条件肯定地表达为TT.STATUS IN ('D','J','L', 'S')
,那么优化器可能能够使用 SKIP SCAN 来评估索引上的过滤器。
但是,如果将 TRX_T.TYPE
用作过滤器(或者将索引列的顺序重新排列为在 TYPE
之前具有 STATUS
但不要这样做,则索引使用会更有效)它可能会破坏其他查询的稳定性)。
另一种选择是将表达式重写为 NOT IN 子查询(如果 (TRX_T.CODE, TRX_T.SUBID)
中有 no 空值,否则为 NOT EXISTS 子查询):
select * from TRX_T TT, TRX_SUB TS
where TT.CODE=TS.CODE
and TT.SUBID= TS.ID
and TS.VALUE=1
and TS.CODE=1
AND TS.ID=17
AND (TT.CODE, TT.SUBID) NOT IN
(select x.CODE, x.SUBID
from trx_t x
where x.status in ('T','R','C')
)
但是,在该列表中具有 STATUS 值的 TRX_T 记录的数量非常大 - 它们是您的表的大部分 - 因此评估该子查询可能比您目前拥有的更昂贵。
请注意,通常的警告适用。在 *** 上调优查询是个小游戏。缺少太多信息(数据量、偏差、其他索引、解释计划等),我们只能做猜测之外的任何事情。
【讨论】:
是的,我同意调优查询在 SO 上有点棘手。这就是我要求重新编写查询的原因,我可以创建一个覆盖索引来覆盖它可能有帮助的所有列。根据TT.STATUS IN ('D','J','L', 'S')
,有时会插入新状态,因此不会有帮助。
这就是杯子游戏的确切原因。我们所能做的就是为您建议不同的方法来重写您的查询,大多数情况下不会有任何改进,甚至会使事情变得更糟,或者由于某些其他原因(例如新状态)而不起作用。跨度>
【参考方案2】:
使用显式加入而不加入可以用下面的方式替换
select * from TRX_T TT
join TRX_SUB TS
on TT.CODE=TS.CODE and TT.SUBID= TS.ID
where TS.VALUE=1
and TS.CODE=1
AND TS.ID=17
and not exists
( select 1 from TRX_T t1 where t1.CODE=TT.code
and (t1.STATUS ='T' OR t1.STATUS='R' or t1.STATUS='C')
)
【讨论】:
这不是等效查询。您需要使用布尔 AND 而不是 OR。【参考方案3】:您可以尝试使用left join
并过滤掉TT2.status is null
,这样它就会为您提供not in ('T','R','C')
的记录
select * from TRX_T TT
join TRX_SUB TS on TT.CODE=TS.CODE and TT.SUBID= TS.ID
left join (select * from TRX_T where STATUS in ('T','R','C')) TT2 on
TT.CODE=TT2.CODE and TT.SUBID= TT2.SUBID
where TS.VALUE=1 and TS.CODE=1 AND TS.ID=17 and TT2.status is null
【讨论】:
【参考方案4】:你可以使用minus
设置运算符作为
select *
from TRX_T TT
join TRX_SUB TS
on ( TT.CODE = TS.CODE
and TT.SUBID = TS.ID )
where TS.VALUE = 1
and TS.CODE = 1
and TS.ID = 17
minus
select *
from TRX_T TT
join TRX_SUB TS
on ( TT.CODE = TS.CODE
and TT.SUBID = TS.ID )
where TS.VALUE = 1
and TS.CODE = 1
and TS.ID = 17
and TT.STATUS in ('T', 'R', 'C');
附:是的,Not in
从性能的角度来看主要是有问题的,作为不使用nvl
功能的一方,在使用Not in
时不应该忘记。
【讨论】:
这是否更有效取决于评估 TRXT_T 和 TRX_SUB 上的连接的成本。【参考方案5】:试试下面,你可以使用'except':
SELECT *
FROM trx_t TT,
trx_sub TS
WHERE TT.code = TS.code
AND TT.subid = TS.id
AND TS.value = 1
AND TS.code = 1
AND TS.id = 17
EXCEPT
SELECT *
FROM trx_t TT,
trx_sub TS
WHERE TT.status = 'T'
OR TT.status = 'R'
OR TT.status = 'C'
【讨论】:
很高兴看到有人对此表示赞同。显然他们也不知道 EXCEPT 在 Oracle 中是无效的。它应该是 MINUS。 唉,没有证据表明这会导致更有效的执行计划。 ('T','R' and 'C') 的状态值占 TRX_T 中所有行的 90% 以上,因此组装较低子查询的结果集将是昂贵的。那是在我们考虑产品的灾难性影响之前,我们将从 TRX_T 和 TRX_SUB 的交叉连接中得到。 哦,对不起,我认为它的 sql 正如@APC 所说,这些都只是猜测。我建议获取 SQL Monitor 报告并提供事实。 你是比尔盖茨以上是关于重写查询替换不在的主要内容,如果未能解决你的问题,请参考以下文章