存在Oracle 10gR2慢查询

时间:2013-11-27 08:15:05

标签: sql oracle query-optimization

我是Oracle的新手。 我写了一个约需40秒的查询

SELECT count(*) FROM (
 SELECT * FROM (SELECT r.idra FROM hm.Ra r 
       WHERE (r.rxdate BETWEEN to_date('2013-09-01','YYYY-MM-DD') and to_date('2013-11-01','YYYY-MM-DD')) 
and (r.idstato IN (1,2,3,4,5,6)) 
and ((r.idgruppo IN (864,863...,595))  
      or (r.curridgroup IN (864,863...,595)) 
      or (EXISTS (SELECT rev.idra FROM hm.Raevent rev WHERE rev.idra = r.idra AND rev.idgroup IS NOT NULL AND rev.idgroup IN (864,863...,595)))  ) ) ) r1 

稍微搜索一下后,我用内连接重写了查询,大概需要1秒:

select count(*)
FROM
(SELECT r.idra FROM hm.v_Ra r 
 WHERE (r.rxdate BETWEEN to_date('2013-09-01','YYYY-MM-DD') and to_date('2013-11-01','YYYY-MM-DD')) 
and (r.idstato IN (1,2,3,4,5,6)))  rd
inner join 
((SELECT idra FROM hm.Ra r
WHERE  ((r.idgruppo IN (864,863...,595)) 
       or (r.curridgroup IN (864,863...,595))) 
UNION
SELECT rev.idra FROM hm.Raevent rev WHERE  rev.idgroup IS NOT NULL 
        AND rev.idgroup IN (864,863...,595) 
)) rr on rr.idra = rd.idra  

我的问题是:为什么oracle(版本10gr2)无法优化第一个? 另外,是否有另一种(更快)的方法来编写相同的查询?

注意:列表(864,863 ...,595)有大约30个元素,为方便起见我写了3。 “Ra”是父表,并且有大约150k行,rxdate,idgruppo,curridgroup,idstato上的索引。 “Raevent”是一个子表,有180k行,索引(idra,idgroup),idgroup。

我运行sql分析器,在两个查询中它都没有给我任何建议。

1 个答案:

答案 0 :(得分:1)

清楚的问题和良好的信息,太棒了!

您正在运行OR-clauses的优化问题。这是一个已知的限制,可以追溯到Oracle 6,导致基于规则的优化器出现问题。我已经在查询上做了很多笔记来帮助你。

首先,始终以可读格式编写查询,以识别人类的结构。解析器并不关心它是如何布局的,但人类会这样做: - )

我已经习惯了旧的Oracle QMS / CDM风格,并增加了一些功能。这种风格还反映了IBM完成的软件工程研究和信息人体工程学的经验。本质上,查询以垂直拉伸格式写下,垂直列和小写字母的使用,因为小写总是人类的头脑更容易识别文本(与几乎闭合的眼睛相比:'PERSONEELSDOSSIER'与'Personeelsdossier')。重写布局后,查询为:

select count (*)
from   ( select *
         from   ( select r.idra
                  from   hm.ra r
                  where  r.rxdate between to_date ('2013-09-01', 'yyyy-mm-dd') and to_date ('2013-11-01', 'yyyy-mm-dd')
                  and    r.idstato in (1, 2, 3, 4, 5, 6)
                  and    ( r.idgruppo in (864, 863...,595)
                           or r.curridgroup in (864, 863..., 595)
                           or ( exists 
                                ( select rev.idra 
                                  from   hm.raevent rev 
                                  where  rev.idra = r.idra 
                                  and    rev.idgroup is not null 
                                  and rev.idgroup in (864, 863..., 595)
                                )
                              )  
                         ) 
                ) 
       ) r

然后,您可以提出一些改善绩效的建议:

select count (*)
from   ( select 1 /* Note 1. */
         from   ( select r.idra /* Note 2. */
                  from   hm.ra r
                  where  r.rxdate between to_date ('2013-09-01', 'yyyy-mm-dd') and to_date ('2013-11-01', 'yyyy-mm-dd') /* Note 3. */
                  and    r.idstato in (1, 2, 3, 4, 5, 6)
                  and    ( r.idgruppo in (864, 863...,595)
                           or r.curridgroup in (864, 863..., 595) /* Note 6. */
                           or ( exists /* Note 5. */
                                ( select 1 /* Note 4. */
                                  from   hm.raevent rev 
                                  where  rev.idra = r.idra 
                                  and    rev.idgroup is not null 
                                  and    rev.idgroup in (864, 863..., 595)
                                )
                              )  
                         ) 
                ) 
       ) r

建议如下:

  1. 注意1:获取所有列可能会导致进行全表扫描,但您只需要通常可以使用必需字段上的小索引来完成的计数。请注意,Oracle不会在索引中存储空值,这与SQL Server不同。
  2. 注意2:将查询折叠到外部级别并再次将r.idra更改为1。不必要的嵌套使解析变得更加困难,经过一段时间后,Oracle就会停止解析并选择迄今为止最好的执行计划。
  3. 注意3:在日期上使用from之间很好,它允许使用索引。在这个限制性很强的日期范围内检查hm.ra.rxdate是否有索引。
  4. 注4:请参阅更改1,但现在查看一列。
  5. 注5:考虑使用IN而不是EXISTS。表中的大卷可能会更快,但输出量较低,因为它不会将子查询与外部查询相关联。
  6. 注6:总是尝试将OR重写为UNION或UNION ALL。或者优化并不总是可行的。当使用OR时,总是检查子句是否属于同一类型(A = 1或A = 2或A = 3)。当OR-ed有不同的子句时,索引的使用将被废弃,Oracle优化器将恢复为全表扫描,这可能会花费更多的时间,并且还可能完全有效地清除数据库缓存,从而阻碍交互中的所有其他用户系统。
  7. 生成的查询可能是:

    select sum(c)
    from   ( select count(*) c
             from   hm.ra r
             where  r.rxdate between to_date ('2013-09-01', 'yyyy-mm-dd') and to_date ('2013-11-01', 'yyyy-mm-dd')
             and    r.idstato in (1, 2, 3, 4, 5, 6)
             and    r.idgruppo in (864, 863...,595)
             union all
             select count(*) c
             from   hm.ra r
             where  r.rxdate between to_date ('2013-09-01', 'yyyy-mm-dd') and to_date ('2013-11-01', 'yyyy-mm-dd')
             and    r.idstato in (1, 2, 3, 4, 5, 6)
             and    r.curridgroup in (864, 863..., 595)
             union all
             select count(*) c
             from   hm.ra r
             where  r.rxdate between to_date ('2013-09-01', 'yyyy-mm-dd') and to_date ('2013-11-01', 'yyyy-mm-dd')
             and    r.idstato in (1, 2, 3, 4, 5, 6)
             and    r.idra 
                    in /* Use exists when used interactively in a screen. */
                    ( select distinct rev.idra
                      from   hm.raevent rev 
                      where  rev.idgroup in (864, 863..., 595)
                    )
           )