我正在尝试在Oracle 8i服务器上运行以下PL / SQL(旧的,我知道):
select
-- stuff --
from
s_doc_quote d,
s_quote_item i,
s_contact c,
s_addr_per a,
cx_meter_info m
where
d.row_id = i.sd_id
and d.con_per_id = c.row_id
and i.ship_per_addr_id = a.row_id(+)
and i.x_meter_info_id = m.row_id(+)
and d.x_move_type in ('Move In','Move Out','Move Out / Move In')
and i.prod_id in ('1-QH6','1-QH8')
and d.created between add_months(trunc(sysdate,'MM'), -1) and sysdate
;
然而,执行速度非常慢。由于服务器每晚午夜左右被取消,因此通常无法及时完成。
执行计划如下:
SELECT STATEMENT 1179377
NESTED LOOPS 1179377
NESTED LOOPS OUTER 959695
NESTED LOOPS OUTER 740014
NESTED LOOPS 520332
INLIST ITERATOR
TABLE ACCESS BY INDEX ROWID S_QUOTE_ITEM 157132
INDEX RANGE SCAN S_QUOTE_ITEM_IDX8 8917
TABLE ACCESS BY INDEX ROWID S_DOC_QUOTE 1
INDEX UNIQUE SCAN S_DOC_QUOTE_P1 1
TABLE ACCESS BY INDEX ROWID S_ADDR_PER 1
INDEX UNIQUE SCAN S_ADDR_PER_P1 1
TABLE ACCESS BY INDEX ROWID CX_METER_INFO 1
INDEX UNIQUE SCAN CX_METER_INFO_P1 1
TABLE ACCESS BY INDEX ROWID S_CONTACT 1
INDEX UNIQUE SCAN S_CONTACT_P1 1
如果我改变以下where子句:
and d.created between add_months(trunc(sysdate,'MM'), -1) and sysdate
静态值,例如:
and d.created between to_date('20110101','yyyymmdd') and sysdate
执行计划变为:
SELECT STATEMENT 5
NESTED LOOPS 5
NESTED LOOPS OUTER 4
NESTED LOOPS OUTER 3
NESTED LOOPS 2
TABLE ACCESS BY INDEX ROWID S_DOC_QUOTE 1
INDEX RANGE SCAN S_DOC_QUOTE_IDX1 3
INLIST ITERATOR
TABLE ACCESS BY INDEX ROWID S_QUOTE_ITEM 1
INDEX RANGE SCAN S_QUOTE_ITEM_IDX4 4
TABLE ACCESS BY INDEX ROWID S_ADDR_PER 1
INDEX UNIQUE SCAN S_ADDR_PER_P1 1
TABLE ACCESS BY INDEX ROWID CX_METER_INFO 1
INDEX UNIQUE SCAN CX_METER_INFO_P1 1
TABLE ACCESS BY INDEX ROWID S_CONTACT 1
INDEX UNIQUE SCAN S_CONTACT_P1 1
几乎立即开始返回行。
到目前为止,我已经尝试用绑定变量替换动态日期条件,以及使用从双表中选择动态日期的子查询。到目前为止,这些方法都没有帮助提高性能。
因为我对PL / SQL比较陌生,所以我无法理解执行计划中存在这些实质性差异的原因。
我也试图将查询作为SAS的传递来运行,但为了测试执行速度,我一直在使用SQL * Plus。
编辑:
为了澄清,我已经尝试使用绑定变量,如下所示:
var start_date varchar2(8);
exec :start_date := to_char(add_months(trunc(sysdate,'MM'), -1),'yyyymmdd')
使用以下where子句:
and d.created between to_date(:start_date,'yyyymmdd') and sysdate
返回执行成本1179377。
我还想尽可能避免使用绑定变量,因为我不相信我可以从SAS传递查询中引用它们(尽管我可能错了)。
答案 0 :(得分:8)
我怀疑这里的问题与ADD_MONTHS函数的执行时间有很大关系。您已经证明,当您使用硬编码的最短日期时,执行计划存在显着差异。执行计划的重大变化通常对运行时间的影响远大于函数调用开销,尽管可能不同的执行计划可能意味着该函数被调用了很多次。无论哪种方式,要查看的根本问题是您没有获得所需的执行计划。
良好的执行计划从S_DOC_QUOTE_IDX1
上的范围扫描开始。鉴于查询更改的性质,我认为这是CREATED
列的索引。当过滤条件基于SYSDATE
时,优化器通常不会选择在日期列上使用索引。因为直到执行时才对其进行评估,所以在确定执行计划之后,解析器无法很好地估计日期过滤条件的选择性。当您使用硬编码的开始日期时,解析器可以使用该信息来确定选择性,并对索引的使用做出更好的选择。
我也建议使用绑定变量,但我认为因为你在8i上,优化器无法查看绑定值,所以这就像以前一样在黑暗中留下它。在以后的Oracle版本中,我希望绑定解决方案有效。
然而,这是一个很好的情况,使用文字替换可能比使用绑定变量更合适,因为(a)开始日期值不是用户指定的,(b)整个月它将保持不变,所以你不会解析很多略有不同的查询。
所以我的建议是编写一些代码来确定开始日期的静态值,并在解析之前将其直接连接到查询字符串中。执行。
答案 1 :(得分:6)
首先,你获得不同执行时间的原因并不是因为Oracle经常执行日期函数。执行这个SQL函数,即使它是为每一行(可能不是那样)完成的,与从磁盘/内存中实际检索行所花费的时间相比,只需要可忽略的时间。
您的执行时间完全不同,因为您已经注意到,Oracle会选择不同的访问路径。选择一个访问路径而不是另一个访问路径可能导致执行时间差异的大小顺序。因此,真正的问题不是“为什么add_months
需要时间?”但是:
为什么Oracle在选择效率更高的路径时会选择这种特定的无效路径?
要回答这个问题,必须了解优化器的工作原理。 optimizer通过估计多个访问路径的成本(如果只有几个表,则为所有访问路径)并选择预期最有效的执行计划来选择特定的访问路径。确定执行计划成本的算法具有规则,并根据从您的数据收集的统计数据进行估算。
作为所有估算算法,它会对您的数据进行假设,例如基于列的最小值/最大值,基数以及段中值的物理分布(聚类因子)的一般分布。
这如何适用于您的特定查询
在您的情况下,优化器必须估计不同过滤器子句的选择性。在第一个查询中,过滤器位于两个变量(add_months(trunc(sysdate,'MM'), -1) and sysdate
)之间,而在另一种情况下,过滤器位于常量和变量之间。
它们看起来与你相同,因为你已经用它的值替换了变量,但是对于优化器,情况非常不同:优化器(至少在8i中)只为特定查询计算一次执行计划。一旦确定了访问路径,所有进一步的执行将获得相同的执行计划。因此,它不能通过其值替换变量,因为该值可能在将来发生变化,并且访问计划必须适用于所有可能的值。
由于第二个查询使用变量,优化器无法准确确定第一个查询的选择性,因此优化器会进行猜测,这会导致您的计划出现错误。
当优化程序未选择正确的计划时,您可以执行哪些操作
如上所述,优化器有时会进行错误的猜测,从而导致访问路径次优。即使它很少发生,这可能是灾难性的(几小时而不是几秒钟)。以下是您可以尝试的一些操作:
last_analyzed
和ALL_TABLES
上的ALL_INDEXES
列会告诉您最后一次收集这些对象的统计信息的时间。良好的可靠统计数据可以提供更准确的猜测,从而(希望)能够更好地执行计划。dbms_stats
包)这真是一个有趣的话题。 oracle优化器不断变化(在发行版之间)它会随着时间的推移而改进,即使有时会因缺陷得到纠正而引入新的怪癖。如果你想了解更多,我建议Jonathan Lewis'Cost Based Oracle: Fundamentals
答案 2 :(得分:1)
那是因为每次比较都会运行该功能。
有时将它放在双重选择中会更快:
and d.created
between (select add_months(trunc(sysdate,'MM'), -1) from dual)
and sysdate
否则,您也可以加入这样的日期:
select
-- stuff --
from
s_doc_quote d,
s_quote_item i,
s_contact c,
s_addr_per a,
cx_meter_info m,
(select add_months(trunc(sysdate,'MM'), -1) as startdate from dual) sd
where
d.row_id = i.sd_id
and d.con_per_id = c.row_id
and i.ship_per_addr_id = a.row_id(+)
and i.x_meter_info_id = m.row_id(+)
and d.x_move_type in ('Move In','Move Out','Move Out / Move In')
and i.prod_id in ('1-QH6','1-QH8')
and d.created between sd.startdate and sysdate
最后一个选项,实际上是提高性能的最佳机会:在查询中添加日期参数,如下所示:
and d.created between :startdate and sysdate
[编辑] 对不起,我看到你已经尝试过像这样的选项。仍然奇怪。如果常量值有效,则绑定参数也应该起作用,只要将add_months函数保留在查询之外即可。
答案 3 :(得分:0)
这是SQL。您可能希望使用PL / SQL并首先将计算add_months(trunc(sysdate,'MM'),-1)保存到变量中,然后绑定它。
此外,我已经看到SAS计算需要很长时间,因为通过网络提取数据并在其处理的每一行上执行额外的工作。根据您的环境,您可以考虑创建一个临时表来存储这些连接的结果,然后点击临时表(尝试CTAS)。