【问题标题】:Oracle - Tuning queries with date rangeOracle - 使用日期范围调整查询
【发布时间】:2017-03-07 05:15:04
【问题描述】:

我有 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');

在我观察到相同行为的不同环境中针对类似表运行查询:

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。

【问题讨论】:

  • 如果无法访问基本诊断工具,例如解释计划或 SQL 监视器,您的所有答案都将是猜测。我鼓励您访问这些工具,以便我们更好地为您提供帮助。
  • 嗨@BobC。我从一个不同的环境运行了一个解释计划,在那里我看到了类似的行为。

标签: sql oracle


【解决方案1】:

尝试改变这个:

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

【讨论】:

  • 非常感谢@OldProgrammer!
【解决方案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 部分中检查的。 (即使没有索引,它仍然是额外的开销;使用索引 this - 或大多数函数调用 - 将阻止使用索引)。

您应该比较正确的数据类型,有无索引,尤其是有索引。如果列数据类型是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() 相同。

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

【讨论】:

  • 非常感谢,@Alex Poole。
  • 我有一个 group by 类似于这个 group by - to_char(t.start_ts,'YYYY-MM-DD')。这很好还是在我转换为角色时会有任何开销?
  • @PunterVicky - 您正在转换为选择列表中的字符串,因此在 group-by 中执行此操作不会增加任何实际开销,不。这只会发生在where 子句选择的行上,因此(希望)一个比以前小得多的子集 - 并且不会影响索引的使用。 (如果您的所有值都没有小数秒,您可以 group by t.start_ts,但如果有 小数秒会改变结果,那么选择列表会更清晰, group by 和 order by match - 避免对您正在做的事情产生任何混淆。)
  • 再次感谢@Alex Poole。关于这个的最后一个问题,我看到有一个复合索引 - start_ts 和 srvc_operation_name。基于存在复合索引的事实,我可能需要做些什么来加快查询速度?
【解决方案3】:

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

alter session set timed_statistics=ALL;

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

【讨论】:

    猜你喜欢
    • 2014-06-09
    • 2014-05-20
    • 2015-12-18
    • 2020-07-28
    • 2014-05-17
    • 2021-07-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多