为什么检查null会减慢此查询的速度?

时间:2016-07-29 11:33:19

标签: sql oracle performance oracle11g

我得到的这个表包含7,000条记录

desc ARADMIN.V_PKGXMLCODE

Name                  Null     Type          
--------------------- -------- ------------- 
REQUEST_ID            NOT NULL VARCHAR2(15)  
AVAILABILITY                   VARCHAR2(69)  
XML_CODE                       CLOB          
PACKAGENAME_UNIQUE             VARCHAR2(50)  
CATALOG                        NUMBER(15)    
CHILD                          VARCHAR2(255) 
CLASSIFICATION_SYSTEM          NUMBER(15)    
E_MAIL                         VARCHAR2(69)

查询

SELECT COUNT(*) FROM ARADMIN.V_PKGXMLCODE WHERE (CATALOG <> 0 AND CATALOG <> 2) AND (NOT (CHILD IS NULL));

只需不到一秒钟。

查询

SELECT COUNT(*) FROM ARADMIN.V_PKGXMLCODE WHERE (CATALOG IS NULL OR (CATALOG <> 0 AND CATALOG <> 2)) AND (NOT (CHILD IS NULL));

需要23秒。

然而,解释计划声称应该快速实现......

enter image description here

我该怎么办?

4 个答案:

答案 0 :(得分:3)

我能想到的唯一方法是在执行速度上获得这种差异,那就是(a)在field4上有一个索引,而(b)有一个很多为空数据块;可能是由于重复的直接路径载荷导致的高水位设置非常高。

第一个查询仍将使用索引并按预期执行。但由于空值未编入索引,因此索引不能用于检查or field4 is null条件,因此它将回退到全表扫描。

这本身不应该是一个问题,因为7000行的全表扫描不应该花费很长时间。但由于 花了这么长时间,其他事情正在发生。全表扫描必须检查分配给表的每个数据块以查看它们是否包含任何行,并且它所花费的时间表明,即使使用内联CLOB存储,也需要比存储7000行更多的块。

获取大量空数据块的最简单方法是获取大量数据,然后删除大部分数据。但是我相信你在一个现已删除的评论中说过,之前的问题是性能过去没问题而且情况更糟。如果您执行direct-path inserts,则会发生这种情况,特别是如果您通过删除数据然后以直接路径模式插入新数据来“刷新”数据。您可以使用具有/*+ append */提示的插入来执行此操作;或并行;或通过SQL * Loader。每当你这样做时,高水位标记就会移动,因为旧的空白块不会被重复使用;并且每次检查空值的查询的性能都会降低一点。经过大量的迭代,真正开始加起来。

您可以检查数据字典以查看为表格分配了多少空间(user_segments等),并将其与您认为实际拥有的数据大小进行比较。您可以通过重建表来重置HWM,例如:

alter table mytable move;

(最好在维护窗口!)

作为演示,我运行了一个循环到直接路径插入并删除了7000行超过一百次,然后运行了两个查询。第一个花了0.06秒(其中大部分是SQL Devleoper开销);第二次拿下1.260。 (我也跑了Gordon's,它有相似的时间,因为它仍然需要做FTS)。随着更多的迭代,差异将变得更加明显,但我的空间不足......然后我做了alter table move并重新运行了第二个查询,然后花了0.05秒。

答案 1 :(得分:0)

这很有趣。我希望查询具有相同的性能,因为Oracle有一个很好的优化器,不应该被NULL混淆。

此版本的性能如何?

select x1.cnt + x2.cnt + x3.cnt
from (select count(*) as cnt
      from MYTABLE
      where field4 = 1 and child is not null
     ) x1 cross join
     (select count(*) as cnt
      from MYTABLE
      where field4 = 4 and child is not null
     ) x2 cross join
     (select count(*) as cnt
      from MYTABLE
      where field4 is null and child is not null
     ) x3;

此版本应该能够利用MYTABLE(field4, child)上的索引。

答案 2 :(得分:0)

我实际上正在努力解决类似的问题。我有一个条件,需要从查询中过滤掉所有NULL值。

我从开始:

ColumnName IS NOT NULL

这增加了我的查询时间,在此之后我尝试了很多事情,例如一些函数,尽管我的工作效果不佳,但我只会返回我需要的东西。最终,做了一个小小的改动就达到了效果,我所做的就是:

IsNull(ColumnName,'') <> ''

它奏效了,尽管能奏效,但我不确定是否有什么区别。

答案 3 :(得分:0)

IS NULL 不适用于计数。您收到错误“调用本机函数‘ISNULL’时参数计数不正确”