2017-03-06 53 views
0

我有两个表都有开始日期和结束日期。这两个表都与名为cust_id的列相关联。我加入这些表来获取特定的列并按日期范围限制它应用于一个表上。不管日期范围是4天还是1小时,我都会看到查询需要50-55秒。当我提供更小的日期范围时,我假定Oracle需要解析的行数较少。这是预期的行为,还是我应该查找一些东西?Oracle - 调整具有日期范围的查询

select to_char(t.start_ts,'YYYY-MM-DD HH24:MI'), 
COUNT(CASE WHEN f.fault = 'N' THEN 1 END) success, 
COUNT(CASE WHEN f.fault = 'Y' THEN 1 END) failure 
from customer t,profile f where 1=1 
and t.cust_id = f.cust_id 
and to_char(t.start_ts,'YYYY-MM-DD HH24:MI:SS') between '2017-03-01 00:00:00' 
and '2017-05-01 23:59:59' 
group by to_char(t.start_ts,'YYYY-MM-DD HH24:MI') 
order by to_char(t.start_ts,'YYYY-MM-DD HH24:MI'); 

然在不同的Env对类似表的查询我在那里观察相同的行为:

Plan hash value: 2851258613 


    --------------------------------------------------------------------------------------------------------------------------- 
    | Id | Operation        | Name    | Rows | Bytes | Cost (%CPU)| Time  | Pstart| Pstop | 
    --------------------------------------------------------------------------------------------------------------------------- 
    | 0 | SELECT STATEMENT      |     |  2 | 362 | 11651 (1)| 00:00:01 |  |  | 
    | 1 | SORT GROUP BY      |     |  2 | 362 | 11651 (1)| 00:00:01 |  |  | 
    | 2 | NESTED LOOPS      |     |  2 | 362 | 11650 (1)| 00:00:01 |  |  | 
    | 3 | NESTED LOOPS      |     |  2 | 362 | 11650 (1)| 00:00:01 |  |  | 
    | 4 |  PARTITION RANGE ALL    |     |  2 | 284 | 11644 (1)| 00:00:01 |  1 | 41 | 
    |* 5 |  TABLE ACCESS BY LOCAL INDEX ROWID| TXNS    |  2 | 284 | 11644 (1)| 00:00:01 |  1 | 41 | 
    |* 6 |  INDEX SKIP SCAN     | XIE1TXNS   |  4 |  | 11641 (1)| 00:00:01 |  1 | 41 | 
    |* 7 |  INDEX RANGE SCAN     | XAK1FRONTEND_DTLS |  1 |  |  2 (0)| 00:00:01 |  |  | 
    | 8 | TABLE ACCESS BY GLOBAL INDEX ROWID | FRONTEND_DTLS  |  1 | 39 |  3 (0)| 00:00:01 | ROWID | ROWID | 
    --------------------------------------------------------------------------------------------------------------------------- 

    Predicate Information (identified by operation id): 
    --------------------------------------------------- 

     5 - filter("T"."SRVC_NAME"='ControllerSvc' AND "T"."SRVC_VERSION"='10.00' AND 
        "T"."SRC_SERV_ID"<>'test' AND "T"."SRC_SERV_ID"<>'endtoendtesting' AND "T"."SRVR_NODE_NAME" NOT LIKE 
        '%test.net' AND "T"."SRC_SERV_ID"<>'test' AND "T"."SRC_SERV_ID"<>'SYN') 
     6 - access("T"."SRVC_OP_NAME"='getTestInfo') 
      filter("T"."SRVC_OP_NAME"='getTestInfo' AND TO_CHAR(INTERNAL_FUNCTION("T"."START_TS"),'YYYY-MM-DD 
        HH24:MI:SS')>='2017-03-01 00:00:00' AND TO_CHAR(INTERNAL_FUNCTION("T"."START_TS"),'YYYY-MM-DD 
        HH24:MI:SS')<='2017-05-01 23:59:59') 
     7 - access("T"."TXN_ID"="F"."TXN_ID") 

PS: 我无法查找解释计划,因为我没有足够的访问。

+0

没有获得基本的诊断工具,如解释计划或能力SQL监视器,你所有的答案将是猜测。我鼓励你访问这些工具,以便我们更好地帮助你。 – BobC

+0

Hi @BobC。我从一个不同的env开始了解释计划,我在那里看到类似的行为。 –

回答

2

目前还不清楚您是否有start_ts的索引,但您期望更短的时间跨度应该返回更快的结果你可能有。如果你不这样做,你可能需要添加一个。在那里有索引,你查询的方式会阻止它被使用。你正在做的:

and to_char(t.start_ts,'YYYY-MM-DD HH24:MI:SS') between '2017-03-01 00:00:00' 

和“2017年5月1日23:59:59”

这意味着什么每一行(即其他谓词匹配)都必须有start_ts值转换为字符串,然后你将该字符串与另外两个固定字符串进行比较。虽然这会起作用,但速度很慢。您可以从解释计划中看到该列正在filter部分中检查,而不是access部分。 (即使没有索引,它仍然是额外的开销;使用索引(或大多数函数调用)将阻止索引被使用)。

您应该比较正确的数据类型,有无索引,但特别是索引。如果列数据类型是DATE那么你可以做:

and t.start_ts between to_date('2017-03-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS') 
    and to_date('2017-05-01 23:59:59', 'YYYY-MM-DD HH24:MI:SS') 

,或者如果它是一个TIMESTAMP(顾名思义),你可以这样做:

and t.start_ts between to_timestamp('2017-03-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS') 
    and to_timestamp('2017-05-01 23:59:59', 'YYYY-MM-DD HH24:MI:SS') 

,但将跳过带有小数秒任何时间 - 例如23:59:59.543 - 因此可能导致错误的结果。它的安全做到:

and t.start_ts >= timestamp '2017-03-01 00:00:00' 
and t.start_ts < timestamp '2017-05-02 00:00:00' 

...这里我也切换到timestamp literals使其更短,但它与使用to_timestamp()与8种格式掩码。

Oracle可能仍然决定不使用索引(如果存在);或者可能会继续首先使用分区修剪(或者改为)。它取决于所使用的所有谓词的数据和选择性,优化器选择最佳方法。使用正确的数据类型和而不是阻止任何可用的索引提供给它一个更好的选择最佳计划的机会。

+0

非常感谢@Alex Poole。 –

+0

我有一个与此组相似的组,由 - to_char(t.start_ts,'YYYY-MM-DD')。这是好的还是会有任何开销,因为我正在转换为角色? –

+1

@PunterVicky - 您正在转换为选择列表中的字符串,因此在group-by中执行该操作并不会增加任何实际开销,不会。这只会发生在'where'子句选择的行上,所以(希望)会比以前小得多 - 并且不会影响索引使用。 (如果你所有的值都没有小数秒*你可以*通过t.start_ts来分组,但是如果*有*分数秒将会改变结果,并且更清楚地得到选择列表,匹配 - 避免任何关于你在做什么的混淆。) –

3

尝试修改此:

and to_char(t.start_ts,'YYYY-MM-DD HH24:MI:SS') between '2017-03-01 00:00:00' 
and '2017-05-01 23:59:59' 

这样:

and t.start_ts between to_date('2017-03-01 00:00:00','YYYY-MM-DD HH24:MI:SS') 
    and to_date('2017-05-01 23:59:59','YYYY-MM-DD HH24:MI:SS') 

调用上一列功能可以防止指数被正确使用。这假定你有一个关于start_ts的索引。或者(但我会建议第一个选项),是创建一个基于功能的索引 - https://oracle-base.com/articles/8i/function-based-indexes

+0

非常感谢@OldProgrammer! –

0

为了看到运行时统计,看看你的基数估计是准确的,你应该这样做:

alter session set timed_statistics=ALL; 

select * from table(dbms_xplan.display_cursor(null, null, 'ALLSTATS LAST'));