我有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,因为我没有足够的访问权限。
答案 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;