甲骨文 - 有条件的where子句中的性能问题

时间:2019-01-30 18:27:46

标签: oracle where database-performance

考虑两个表,以这种方式连接:

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"(+))   

谢谢你, 大卫

3 个答案:

答案 0 :(得分:1)

or子句中添加where时,部分条件是参数或函数,则数据库不能仅使用索引。这是因为存在“另一种选择”,而基于成本的优化器(CBO)通常选择“全表扫描”。您可以使CBO的查询更简单-例如:

  1. 在应用程序级别选择基于:all值创建不同的where子句,

  2. 使用一些使条件看起来简单的技巧-而不是使用: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个值:

  • 0-表示特定行
  • 1-表示所有行
  • 2-表示table_2.desired_id中所有具有NULL的行

然后查询将是:

-- 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