考虑两个表,以这种方式连接:
select *
from table_1
left join table_2
on table_1.lnk_id = table_2.lnk_id
;
上就TABLE_1“lnk_id”,并在TABLE_2的索引。这将返回8000万行。
我正在使用条件where子句,其变量由前端(APEX)设置:
:all:如果= 1,则应返回所有行。
:desired_id:我想返回的对象。可以为空值,在这种情况下,我只想返回空值行。
我首先对此进行了编码:
select *
from table_1
left join table_2
on table_1.some_id = table_2.some_id
where (
case when :all = 1
then 1
when :desired_id is null and table_2.desired_id is null
then 1
when :desired_id = table_2.desired_id
then 1
else 0
end = 1
)
假设:所有= 0和:desired_id =一些非空值以选择行的用户期望,我体验可怕性能
我了解到我必须避免在“ where”子句中使用“ case”,因此适用于:
where (
:all = 1
or (:desired_id is null and table_2.desired_id is null)
or :desired_id = table_2.desired_id
)
没有机会,这和“案例”解决方案一样慢。
我意识到了这一点:
where (:desired_id = table_2.desired_id);
-> 0.047s-超快
where (:desired_id = table_2.desired_id or 0 = 1);
-> 0.062s-超快
where (:desired_id = table_2.desired_id or :all = 1);
-> 235s -超级慢
因此,使用where(... = ...或... = 1)构造,我绝对可以立即在我的80 M行中找到所需的对象:当我使用时,优化器必须做出错误的决定:全部。
会有人能够引导?
如果可能的话,我宁愿避免使用动态查询构建解决方案,因为它会使我相信的实现和管理变得更加复杂,而且听起来确实...应该与简单的SQL一起使用。
- 编辑以添加计划 -
select *
from table_2
left join table_1
on table_2.lnk_id = table_1.lnk_id
where (:desired_id = table_1.desired_id or 0 = 1);
Plan hash value: 1995399472
--------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 129 | 42183 | 45 (0)| 00:00:01 | | |
| 1 | NESTED LOOPS | | 129 | 42183 | 45 (0)| 00:00:01 | | |
| 2 | NESTED LOOPS | | 138 | 42183 | 45 (0)| 00:00:01 | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TABLE_1 | 3 | 435 | 7 (0)| 00:00:01 | | |
|* 4 | INDEX RANGE SCAN | TABLE_1_I1 | 3 | | 3 (0)| 00:00:01 | | |
|* 5 | INDEX RANGE SCAN | TABLE_2_I2 | 46 | | 3 (0)| 00:00:01 | | |
| 6 | TABLE ACCESS BY GLOBAL INDEX ROWID | TABLE_2 | 40 | 7280 | 21 (0)| 00:00:01 | ROWID | ROWID |
--------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("TABLE_1"."DESIRED_ID"=:DESIRED_ID)
5 - access("TABLE_2"."LNK_ID"="TABLE_1"."LNK_ID")
explain plan for
select *
from table_2
left join table_1
on table_2.lnk_id = table_1.lnk_id
where (:desired_id = table_1.desired_id or :p3070100_all = 1);
Plan hash value: 94704160
----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 79M| 24G| | 1441K (1)| 00:00:57 | | |
|* 1 | FILTER | | | | | | | | |
|* 2 | HASH JOIN RIGHT OUTER| | 79M| 24G| 484M| 1441K (1)| 00:00:57 | | |
| 3 | TABLE ACCESS FULL | TABLE_1 | 3238K| 447M| | 19152 (1)| 00:00:01 | | |
| 4 | PARTITION RANGE ALL | | 79M| 13G| | 668K (1)| 00:00:27 | 1 |1048575|
| 5 | TABLE ACCESS FULL | TABLE_2 | 79M| 13G| | 668K (1)| 00:00:27 | 1 |1048575|
----------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("TABLE_1"."DESIRED_ID"=:DESIRED_ID OR TO_NUMBER(:P3070100_ALL)=1)
2 - access("TABLE_2"."LNK_ID"="TABLE_1"."LNK_ID"(+))
谢谢你, 大卫
答案 0 :(得分:1)
在or
子句中添加where
时,部分条件是参数或函数,则数据库不能仅使用索引。这是因为存在“另一种选择”,而基于成本的优化器(CBO)通常选择“全表扫描”。您可以使CBO的查询更简单-例如:
在应用程序级别选择基于:all值创建不同的where子句,
使用一些使条件看起来简单的技巧-而不是使用:all参数,而仅使用:desired_id并获取所有结果,只需将“ null”作为值传递即可,那么您可以执行以下操作:
where table_2.desired_id = nvl(:desired_id, table_2.desired_id)
如果table_2.desired_id上有索引,CBO应该选择“范围扫描”或“唯一扫描”(用于唯一索引)。
您应该始终为查询生成解释计划,并使用大型表查找“完整扫描”,“嵌套循环”和“笛卡尔联接”,这是您应避免的事情。
更新(2019-02-01)
当您希望拥有“一个查询中的所有内容”时,有第3个选项,因此在应用程序级别(在两个查询之间进行选择)或使用动态SQL时无需任何其他逻辑。可以选择使用union all
在一个查询中进行2个查询,并以始终仅由数据库运行一部分的方式编写它们。
以下是JPG提出的查询的改进版本:
select *
from table_2
left join table_1 on table_2.lnk_id = table_1.lnk_id
where nvl(:all,2) != 1
and :desired_id = table_2.desired_id
union all
select *
from table_2
left join table_1 on table_2.lnk_id = table_1.lnk_id
where :desired_id = table_2.desired_id
and :all = 1
;
考虑到解释平原可以为此“展示”更多的工作,而不是其他计划,但是数据库应该在运行时将条件之一解析为“总是错误”,并且只查询一部分。
-第二次更新-
好的,我现在在您写的地方读了您的评论之一:
“全部”返回每一行,“无”返回该列具有空值的所有行,“特定值”返回与该特定值匹配的行...
让我们假设所有值可以取3个值:
然后查询将是:
-- Specific row
select *
from table_2
left join table_1
on table_2.lnk_id = table_1.lnk_id
where :all = 0
and :desired_id = table_2.desired_id
union all
-- All rows with null
select *
from table_2
left join table_1
on table_2.lnk_id = table_1.lnk_id
where :all = 2
and table_2.desired_id is NULL
union all
-- All rows
select *
from table_2
left join table_1
on table_2.lnk_id = table_1.lnk_id
where :desired_id = table_2.desired_id
and :all = 1
;
但是您应该知道,简单索引不适用于NULL。因此,如果您在table_2.desired_id上具有索引:
create index idx_table_2_desired_id on table_2(desired_id);
例如,它不起作用,但可能会很复杂。
create index idx_table_2_desired_id on table_2(desired_id, 1);
将允许数据库在此类索引中的desired_id中搜索NULL。
答案 1 :(得分:1)
正如robertus所写,您应该避免在处理索引列的查询中使用“ OR” 我建议将以下查询替换为“ OR”
select *
from table_2
left join table_1
on table_2.lnk_id = table_1.lnk_id
where (:desired_id = table_2.desired_id or :all = 1);
通过功能更强大的解决方案。
select *
from table_2
left join table_1
on table_2.lnk_id = table_1.lnk_id
where :desired_id = table_2.desired_id
union
select *
from table_2
left join table_1
on table_2.lnk_id = table_1.lnk_id
where :desired_id <> table_2.desired_id
and :all = 1
;
答案 2 :(得分:0)
在错误的方案A一目了然解释该问题的原因,这是在这里:
1 - filter("TABLE_1"."DESIRED_ID"=:DESIRED_ID OR TO_NUMBER(:P3070100_ALL)=1)
在优化器不能使用TABLE_1_I1
,因为这个OR条件索引。
根本不要在查询中使用此绑定变量TO_NUMBER(:P3070100_ALL)=1
,而应根据:P3070100
的值使用动态SQL和两个版本的查询。
如果:P3070100 <> 1
使用此查询,这将使用索引和将快速:
select *
from table_2
left join table_1
on table_2.lnk_id = table_1.lnk_id
where :desired_id = table_1.desired_id ;
和当:P3070100 = 1
使用此查询,这将是很慢(因为它加入两个表中的所有行):
select *
from table_2
left join table_1
on table_2.lnk_id = table_1.lnk_id