Oracle - 使用日期范围调整查询

时间:2017-03-06 17:30:40

标签: sql oracle

我有2个表,它们都有开始日期和结束日期。这两个表都与名为cust_id的列相关联。我正在加入这些表以获取特定列,并按照应用于一个表的日期范围对其进行限制。无论日期范围是4天还是1小时,我都会看到查询需要50-55秒。我假设当我提供较小的日期范围时,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: 我无法查找EXPLAIN PLAN,因为我没有足够的访问权限。

3 个答案:

答案 0 :(得分: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

答案 1 :(得分:2)

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

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

和'2017-05-01 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以缩短它,但它与使用{8}格式掩码的to_timestamp()相同。

Oracle可能仍决定不使用索引(如果存在);或者可以继续使用分区修剪(或替代)。它取决于您使用的所有谓词的数据和选择性,以及优化器选择最佳方法。使用正确的数据类型和可以防止任何索引可用,这样可以更好地选择最佳计划。

答案 2 :(得分:0)

为了查看运行时统计信息并查看您的基数估算是否准确,您应该这样做:

Genymotion

alter session set timed_statistics=ALL;
相关问题