Sha*_*eri 3 oracle performance date
我正在尝试在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
;
Run Code Online (Sandbox Code Playgroud)
然而,执行速度非常慢.由于服务器每晚午夜左右被取消,因此通常无法及时完成.
执行计划如下:
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
Run Code Online (Sandbox Code Playgroud)
如果我改变以下where子句:
and d.created between add_months(trunc(sysdate,'MM'), -1) and sysdate
Run Code Online (Sandbox Code Playgroud)
到静态值,例如:
and d.created between to_date('20110101','yyyymmdd') and sysdate
Run Code Online (Sandbox Code Playgroud)
执行计划变为:
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
Run Code Online (Sandbox Code Playgroud)
它几乎立即开始返回行.
到目前为止,我已经尝试用绑定变量替换动态日期条件,以及使用从双表中选择动态日期的子查询.到目前为止,这些方法都没有帮助提高性能.
因为我对PL/SQL比较陌生,所以我无法理解执行计划中存在如此重大差异的原因.
我也试图将查询作为SAS的传递来运行,但为了测试执行速度,我一直在使用SQL*Plus.
编辑:
为了澄清,我已经尝试使用绑定变量,如下所示:
var start_date varchar2(8);
exec :start_date := to_char(add_months(trunc(sysdate,'MM'), -1),'yyyymmdd')
Run Code Online (Sandbox Code Playgroud)
使用以下where子句:
and d.created between to_date(:start_date,'yyyymmdd') and sysdate
Run Code Online (Sandbox Code Playgroud)
返回执行成本1179377.
如果可能的话,我也想避免使用绑定变量,因为我不相信我可以从SAS传递查询中引用它们(尽管我可能错了).
我怀疑这里的问题与ADD_MONTHS函数的执行时间有很大关系.您已经证明,当您使用硬编码的最短日期时,执行计划存在显着差异.执行计划的重大变化通常对运行时间的影响远大于函数调用开销,尽管可能不同的执行计划可能意味着该函数被调用了很多次.无论哪种方式,要查看的根本问题是您没有获得所需的执行计划.
良好的执行计划始于范围扫描S_DOC_QUOTE_IDX1.鉴于查询更改的性质,我假设这是CREATED列的索引.当过滤条件基于时,优化器通常不会选择在日期列上使用索引SYSDATE.因为直到执行时才对其进行评估,所以在确定执行计划之后,解析器无法很好地估计日期过滤条件的选择性.当您使用硬编码的开始日期时,解析器可以使用该信息来确定选择性,并对索引的使用做出更好的选择.
我也建议使用绑定变量,但我认为因为你在8i上,优化器无法查看绑定值,所以这就像以前一样在黑暗中留下它.在以后的Oracle版本中,我希望绑定解决方案是有效的.
但是,这是一个很好的情况,使用文字替换可能比使用绑定变量更合适,因为(a)开始日期值不是用户指定的,并且(b)它将在整个月保持不变,所以你不会解析许多略有不同的查询.
所以我的建议是编写一些代码来确定开始日期的静态值,并在解析和执行之前将其直接连接到查询字符串中.
首先,您获得不同执行时间的原因并不是因为Oracle执行了很多日期函数.执行这个SQL函数,即使它是为每一行(可能不是那样)完成的,与从磁盘/内存中实际检索行所花费的时间相比,只需要可忽略的时间.
您将获得完全不同的执行时间,因为正如您所注意到的,Oracle选择了不同的访问路径.选择一个访问路径而不是另一个访问路径可能导致执行时间差异的大小顺序.因此,真正的问题不是"为什么add_months需要时间?" 但:
为什么Oracle选择这种特别无效的路径而有效率更高?
要回答这个问题,必须了解优化器的工作原理.该优化的(如果只有几桌全部)估计的几个访问路径的成本和选择,预计将是最有效的执行计划选择一个特定的访问路径.确定执行计划成本的算法具有规则,并根据从您的数据收集的统计数据进行估算.
作为所有估算算法,它会对您的数据进行假设,例如基于列的最小值/最大值,基数以及段中值的物理分布(聚类因子)的一般分布.
这是如何适用于您的特定查询
在您的情况下,优化器必须估计不同过滤器子句的选择性.在第一个查询中,过滤器位于两个变量(add_months(trunc(sysdate,'MM'), -1) and sysdate)之间,而在另一个情况下,过滤器位于常量和变量之间.
它们看起来与你相同,因为你已经用它的值替换了变量,但是对于优化器,情况非常不同:优化器(至少在8i中)只为特定查询计算一次执行计划.一旦确定了访问路径,所有进一步的执行将获得相同的执行计划.因此,它不能通过其值替换变量,因为该值可能在将来发生变化,并且访问计划必须适用于所有可能的值.
由于第二个查询使用变量,优化器无法准确确定第一个查询的选择性,因此优化器会进行猜测,这会导致您的计划出现错误.
当优化器没有选择正确的计划时,您可以做什么
如上所述,优化器有时会进行错误的猜测,从而导致访问路径不理想.即使它很少发生,这可能是灾难性的(几小时而不是几秒钟).以下是您可以尝试的一些操作:
last_analyzed上栏ALL_TABLES,并ALL_INDEXES会告诉你,当是统计收集这些对象的最后一次.良好的可靠统计数据可以提供更准确的猜测,从而(更有希望)实现更好的执行计划.dbms_stats包)这真是一个有趣的话题.oracle优化器不断变化(在发行版之间)它会随着时间的推移而改进,即使有时会因缺陷得到纠正而引入新的怪癖.如果你想了解更多,我会建议乔纳森刘易斯的" 基于成本的Oracle:基础知识"