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_START
是 1
而 PARTITION_STOP
是 KEY
现在,如果我将条件更改为从固定日期开始搜索,而不是从 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)可以通过索引访问。优点是,只使用正确的分区范围(对当前行有效的范围)。
目前我们有一个工作查询 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_START
是 1
而 PARTITION_STOP
是 KEY
现在,如果我将条件更改为从固定日期开始搜索,而不是从 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)可以通过索引访问。优点是,只使用正确的分区范围(对当前行有效的范围)。