Oracle 如何在具有日期范围标准的分区 table 上执行 select

How Oracle execute a select on a partitioned table with a date range criteria

目前我们有一个工作查询 select 来自大型分区 table 以供处理。 table 按日范围分区,1 个分区中有 1 天的数据。

查询是:

SELECT /*+parallel(auto)*/ a.*
          FROM TBL_EXCLUDED a
          WHERE (not exists(
                select 1 from C_HISTORY history 
                inner join C_MATRIX_EXT ext on  ext.from_type = (CASE WHEN UPPER(history.TYPE_CD)<>'B' THEN 'C' ELSE 'B' END) and ext.from_channel = history.channel
                where history.mid= a.mid
                and ext.to_channel = a.channel and ext.to_type = (CASE WHEN UPPER(a.TYPE_CD)<>'B' THEN 'C' ELSE 'B' END)
                and history.channel_sent_dt>=TRUNC (SYSDATE-ext.duration+1)
                and history.channel_sent_dt<=TRUNC (SYSDATE)+1
                )   )

说明计划有点长,所以我只展示分区的部分。请注意 PARTITION_START1PARTITION_STOPKEY

现在,如果我将条件更改为从固定日期开始搜索,而不是从 sysdate 派生,成本将发生变化:

SELECT /*+parallel(auto)*/ a.*
          FROM TBL_EXCLUDED a
          WHERE (not exists(
                select 1 from C_HISTORY history 
                inner join C_MATRIX_EXT ext on  ext.from_type = (CASE WHEN UPPER(history.TYPE_CD)<>'B' THEN 'C' ELSE 'B' END) and ext.from_channel = history.channel
                where history.mid= a.mid
                and ext.to_channel = a.channel and ext.to_type = (CASE WHEN UPPER(a.TYPE_CD)<>'B' THEN 'C' ELSE 'B' END)
                and history.channel_sent_dt>=TRUNC ( TO_DATE('10-09-2015','dd-mm-yy')  )
                and history.channel_sent_dt<=TRUNC ( TO_DATE('17-09-2015','dd-mm-yy')  )+1
                )   )

解释现在显示的成本要低得多:

实际执行会不会和这个计划有出入?此外,尽管在 sysdate 的查询中成本很高,但实际执行会少得多吗,因为它应该只能从目标分区 select ?

最后,使用第二种查询范围分区的方式 table 而不是第一种查询会有实际好处吗?

感谢所有回复和指导,谢谢。

解释很简单——Oracle 直到执行时才知道 sysdate 的值是什么,所以它使用 "KEY" 来表示这将在执行时进行评估。在执行时,成本将与输入固定日期相同。

然而,在第一个查询中,

history.channel_sent_dt>=TRUNC (SYSDATE-ext.duration+1)

...优化器推断它无法修剪要扫描的分区的下限,因为它依赖于在解析时未知的连接数据——它根据中的数据而变化另一个 table。因此,分区下限设置为 1。

可能您可以通过使用动态 SQL 来计算分区的实际下限来优化它,但如果实际上下限实际上是 1,那么就没有意义了。

编辑:如果您知道 ext.duration 的最大值将为 7,那么您可以尝试通过以下方式帮助优化器:

and history.channel_sent_dt>=TRUNC (SYSDATE-ext.duration+1)
and history.channel_sent_dt>=TRUNC (SYSDATE-7+1)
and history.channel_sent_dt<=TRUNC (SYSDATE)+1

有兴趣听听这是否有效。

大卫

完整回答的两个小补充点

1) 如果你的持续时间很短并且 "without peeks"

 select max(duration) from C_MATRIX_EXT ext

你可以概括而不是

 and history.channel_sent_dt>=TRUNC (SYSDATE-7+1)

通过添加

 and history.channel_sent_dt>=TRUNC (SYSDATE- (select max(duration) -1 from C_MATRIX_EXT))

您将再次看到 KEY - KEY 范围,但效果与 sysdate 硬限制相同。

2) 由于内部table的记录数较少(估计为82),您可能会受益于NESTED LOOP join。您将需要外部 table(partitined table)可以通过索引访问。优点是,只使用正确的分区范围(对当前行有效的范围)。