编辑:已解决!感谢@kfinity。
AskTom 建议在查询开始时使用select / * + opt_param('_ optimizer_use_feedback''false')* /以禁用反馈用法。这为我解决了这个问题。
tldr:向查询中添加随机注释会使其运行一致,删除该注释会破坏该注释。
警告:很长。
我首选的工作方式是将源中的查询作为字符串使用,以使它们处于版本控制中,并且可以看到随着时间的变化。与此同时,我使用dapper
和oracle.ManagedDataAccess
NuGet包。有问题的应用程序是在.NET Framework 4.7.2上运行的WPF应用程序(在两种情况下)。我正在使用Visual Studio Professional 2017 15.9.5。
大约一年前,我在查询中遇到了此问题。我不记得是什么,我知道我没有时间记录和发布在这里。我现在做,并且遇到了同样的问题。那时我以某种方式弄清楚了,如果我重新启动PC或更改查询文本,它将再次正常运行。偶尔会出现问题的症状,我会在查询中添加一条注释,或者删除以前的注释,然后在每个发行版中测试该特定功能。我会每次对其进行测试,因为如果我的计算机上有故障,那么目标用户计算机上也会有故障。当时我认为这是我的PC的驱动程序/硬件问题。我发现可以通过更改查询文本来解决此问题的方式是,因为我会将整个查询从代码剪切(ctrl-x ctrl-v
)粘贴并粘贴到Oracle developer
,然后在此处进行编辑。在某个时候,我注意到甚至多余的空格或回车键都可以使它再次正常工作。
回到现在,我又遇到了问题。这次有所不同,因为它不再偶尔失败。这是非常一致的。回想一下我如何看待这是一个驱动程序/硬件问题,我在3台不同的计算机上构建并运行了该应用程序,结果都是相同的。我可以在Oracle developer
中运行查询,使用ctrl + end
来运行整个查询,而不仅仅是50行。它运行约10秒钟。它会一遍又一遍地运行,如果没有任何变化,这是有道理的。
如果我从应用程序中运行它,则它可以正常运行-但只能运行一次。大约需要10秒钟。如果刷新数据(再次运行查询),它将挂起。也不例外,如果我中断调试器,它只会使database.Query<>()
调用变得冷淡。如果我重新启动PC或更改查询,它将运行-仅一次。
当然,我去了Google寻求帮助。找到了一些有趣的文章,这些文章并没有真正帮助我:
Oracle inconsistent performance behaviour of query
直到我发现:
https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1191435335912
他们提到v$session_longops
,据说可以使我们深入了解长期运行的操作。
select *
from v$session_longops
where time_remaining > 0
我刚刚刷新应用程序中的数据时运行了此命令,导致查询第二次运行。我看到了:
第一个查询运行正常,索引正常。第二次启动全表扫描。它不需要这样做,因为它在第一次运行时也可以在oracle developer
中正常运行。不出所料,如果我让它运行(花费20分钟以上),表扫描就会完成,并且得到的结果与第一次相同。
我发现您可以使用explain plan for
来解释查询计划,而无需使用Oracle developer
中的GUI。这给了我2个截然不同的计划,Oracle developer
中的一个总是有这个注释:- 'PLAN_TABLE' is old version
。因此,我不确定我是否可以信任此信息,也不知道该怎么做。
就像我之前说过的那样,添加或删除注释或更改查询文本可以解决问题,而且永远不会启动全表扫描。我在查询中添加了一个包含DateTime.Now
的注释,因此它始终是不同的。它始终有效。从技术上来说,这可以解决问题,但我认为这对于一个甚至更荒谬的问题来说是非常荒谬的解决。我宁愿知道为什么会这样,如果我做错了其他事情。所以问题是,为什么随机注释会解决我的查询问题?
某些上下文:这是一个ERP系统,查询将获取具有层次结构或仅在其自身上的所有工作订单,将它们组合在一起,然后添加其所需的材料和一些其他信息,例如其描述。只会对尚未关闭的工作单执行此操作。
SQL:
select
--Hierarchic info, some aliases exceeded 30 chars and had to be shorted to current state
hierarchic_workorders.ccn as HierarchicCcn ,
hierarchic_workorders.mas_loc as HierarchicMasLoc,
hierarchic_workorders.wo_num as HierarchicWoNum,
hierarchic_workorders.wo_line as HierarchicWoLine,
wo.item as HierarchicItem,
wo.revision as HierarchicRevision,
wo_item.description as HierarchicDescription,
wo_rtg.wc as HierarchicWorkCenter,
hierarchic_workorders.startdate as HierarchicStartDate,
hierarchic_workorders.mfgclosedate as HierarchicMfgClosedDate,
hierarchic_workorders.chassisnumbers as HierarchicChassisNumbers,
hierarchic_workorders.wo_level as HierarchicLevel,
hierarchic_workorders.parent_wo_num as HierarchicParentWoNum,
hierarchic_workorders.parent_wo_line as HierarchicParentWoLine,
hierarchic_workorders.parent_wo_bom_useq as HierarchicParentwobomuseq,
--wo bom info
wo_bom.ccn as WoRtgCcn,
wo_bom.mas_loc as WoRtgMasloc,
wo_bom.wo_num as WoRtgWoNum,
wo_bom.wo_line as WoRtgWoLine,
wo_bom.wo_bom_useq as WoRtgWobomUseq,
wo_bom.item as WoRtgItem,
wo_bom.revision as WoRtgRevision,
wo_bom_item.description as WoRtgDescription,
wo_bom.bom_comp_qty as WoRtgBomCompQty,
wo_bom.bom_commit as WoRtgCommit,
wo_bom.backflush as WoRtgBackflush,
wo_bom.issue_qty as WoRtgIssueQty,
wo_bom.commit_qty as WoRtgCommitQty,
wo_bom.reqd_qty as WoRtgReqdQty
from live.wo_bom
--===========================================================================================================================================================================
-- Maybe it's possible to remove this or the other min operation join in hierarchic_workorders, to make it faster - not sure if possible ====================================
--===========================================================================================================================================================================
left join(
select
wo_rtg_min_operation.min_operation,
wo_rtg.*
from live.wo_rtg
left join(
select
ccn,
mas_loc,
wo_num,
wo_line,
lpad(to_char(min(to_number(trim(operation)))), 4, ' ') as min_operation
from live.wo_rtg
group by ccn, mas_loc, wo_num, wo_line
)wo_rtg_min_operation
on wo_rtg_min_operation.ccn = wo_rtg.ccn
and wo_rtg_min_operation.mas_loc = wo_rtg.mas_loc
and wo_rtg_min_operation.wo_num = wo_rtg.wo_num
and wo_rtg_min_operation.wo_line = wo_rtg.wo_line
) wo_rtg
on wo_rtg.ccn = wo_bom.ccn
and wo_rtg.mas_loc = wo_bom.mas_loc
and wo_rtg.wo_num = wo_bom.wo_num
and wo_rtg.wo_line = wo_bom.wo_line
--This case when is painfully slow but it can't really be cached or indexed
and wo_rtg.operation = (
case when wo_bom.operation = ' ' then
wo_rtg.min_operation
else
wo_bom.operation
end
)
--===========================================================================================================================================================================
-- Find all open MPS orders and highest hierarchic PRP orders. Having these be a subquery instead of the starting data drastically increases performance ========================
--===========================================================================================================================================================================
join(
select
ccn,
mas_loc,
wo_num,
wo_line,
startdate,
mfgclosedate,
chassisnumbers,
wo_level,
parent_wo_num,
parent_wo_line,
parent_wo_bom_useq
from (
--===========================================================================================================================================================================
-- PRP ======================================================================================================================================================================
--===========================================================================================================================================================================
select
'PRP' as type,
wowob.ccn,
wowob.mas_loc,
wowob.wo_num,
wowob.wo_line,
apssplit_min_operation.operation_start_date as startdate,
wo.mfg_close_date as mfgclosedate,
trim(
trim(wo.user_alpha2) || ' ' ||
trim(wo.user_alpha3) || ' ' ||
trim(wo.chassis3) || ' ' ||
trim(wo.chassis4) || ' ' ||
trim(wo.chassis5)
) as chassisnumbers,
level as wo_level,
wowob.parent_wo_num,
wowob.parent_wo_line,
wowob.parent_wo_bom_useq
from live.wowob
join live.wo
on wo.ccn = wowob.ccn
and wo.mas_loc = wowob.mas_loc
and wo.wo_num = wowob.wo_num
and wo.wo_line = wowob.wo_line
left join(
select
ccn,
mas_loc,
orderident,
order_line,
lpad(to_char(min(to_number(trim(operation)))), 4, ' ') as min_operation,
operation_start_date
from live.apssplit
where schedule = 'SHOP' and order_type = 'W'
group by ccn, mas_loc, orderident, order_line, operation_start_date
) apssplit_min_operation
on apssplit_min_operation.ccn = wowob.ccn
and apssplit_min_operation.mas_loc = wowob.mas_loc
and apssplit_min_operation.orderident = wowob.wo_num
and apssplit_min_operation.order_line = wowob.wo_line
--Only select open wo's
--Underlying wo's obviously have to start BEFORE their parents, we don't have to check them all for this reason.
where apssplit_min_operation.operation_start_date is not null
and apssplit_min_operation.operation_start_date < sysdate + :days_ahead
--wo.mfg_close_date is null and
--wo.fin_close_date is null and
--wo.ord_qty - wo.scrap_qty - wo.complete_qty > 0
--and wo.start_date < sysdate + :days_ahead
--and wowob.wo_num = ' 334594'
--Grab the childs of only the highest parents.
connect by prior wowob.ccn = wowob.ccn
and prior wowob.mas_loc = wowob.mas_loc
and prior wowob.wo_num = wowob.parent_wo_num
and prior wowob.wo_line = wowob.parent_wo_line
start with wowob.ccn || wowob.mas_loc || wowob.wo_num || wowob.wo_line in (
--Subquery to select all the highest hierarchic wowob's that are still open in wo.
--Performance:
--all: 21253 in ?
--Open only: 174 in 0.155 seconds
select
wowob.ccn || wowob.mas_loc || wowob.wo_num || wowob.wo_line as wowob_key
from live.wowob
--Parent join
left join live.wowob parent_wowob
on wowob.ccn = parent_wowob.ccn
and wowob.mas_loc = parent_wowob.mas_loc
and wowob.parent_wo_num = parent_wowob.wo_num
and wowob.parent_wo_line = parent_wowob.wo_line
--end parent join
where wowob.ccn = :ccn
and wowob.mas_loc = :mas_loc
and parent_wowob.ccn is null
)
union all
--===========================================================================================================================================================================
-- MPS ======================================================================================================================================================================
--===========================================================================================================================================================================
select
'MPS' as type,
wo.ccn,
wo.mas_loc,
wo.wo_num,
wo.wo_line,
apssplit_min_operation.operation_start_date as startdate,
wo.mfg_close_date as mfgclosedate,
trim(
trim(wo.user_alpha2) || ' ' ||
trim(wo.user_alpha3) || ' ' ||
trim(wo.chassis3) || ' ' ||
trim(wo.chassis4) || ' ' ||
trim(wo.chassis5)
) as chassisnumbers,
1 as wo_level,
'' as parent_wo_num,
'' as parent_wo_line,
'' as parent_wo_bom_useq
from live.wo
join live.item_ccn
on item_ccn.ccn = wo.ccn
and item_ccn.item = wo.item
and item_ccn.revision = wo.revision
and item_ccn.mastsched = 'Y' --mps
and item_ccn.planned = ' ' --mrp
and item_ccn.prp = ' ' --NOT prp...
left join(
select
ccn,
mas_loc,
orderident,
order_line,
lpad(to_char(min(to_number(trim(operation)))), 4, ' ') as min_operation,
operation_start_date
from live.apssplit
where schedule = 'SHOP' and order_type = 'W'
group by ccn, mas_loc, orderident, order_line, operation_start_date
) apssplit_min_operation
on apssplit_min_operation.ccn = wo.ccn
and apssplit_min_operation.mas_loc = wo.mas_loc
and apssplit_min_operation.orderident = wo.wo_num
and apssplit_min_operation.order_line = wo.wo_line
--Only select open wo's
--Underlying wo's obviously have to start BEFORE their parents, we don't have to check them all for this reason.
where apssplit_min_operation.operation_start_date is not null
and apssplit_min_operation.operation_start_date < sysdate + :days_ahead
)
order by startdate
) hierarchic_workorders
on hierarchic_workorders.ccn = wo_bom.ccn
and hierarchic_workorders.mas_loc = wo_bom.mas_loc
and hierarchic_workorders.wo_num = wo_bom.wo_num
and hierarchic_workorders.wo_line = wo_bom.wo_line
--===========================================================================================================================================================================
-- Descriptions from wo. wowob and wo_bom are different items and they have different descriptions. =========================================================================
--===========================================================================================================================================================================
left join live.wo
on wo.ccn = hierarchic_workorders.ccn
and wo.mas_loc = hierarchic_workorders.mas_loc
and wo.wo_num = hierarchic_workorders.wo_num
and wo.wo_line = hierarchic_workorders.wo_line
left join live.item wo_item
on wo_item.item = wo.item
and wo_item.revision = wo.revision
left join live.item wo_bom_item
on wo_bom_item.item = wo_bom.item
and wo_bom_item.revision = wo_bom.revision
C#(无效):
using (IDbConnection database = new OracleConnection(_applicationSettings.OracleConnectionString))
{
DynamicParameters parameters = new DynamicParameters();
parameters.Add("ccn", ccn);
parameters.Add("mas_loc", masLoc);
parameters.Add("days_ahead", daysAhead);
return database.Query<HierarchicWoWoBom>(Query, parameters).ToList();
}
C#(可以连续工作):
using (IDbConnection database = new OracleConnection(_applicationSettings.OracleConnectionString))
{
DynamicParameters parameters = new DynamicParameters();
parameters.Add("ccn", ccn);
parameters.Add("mas_loc", masLoc);
parameters.Add("days_ahead", daysAhead);
return database.Query<HierarchicWoWoBom>($"-- {DateTime.Now}: randomized comment so that this query keeps working.\n"
+ Query, parameters).ToList();
}
答案 0 :(得分:1)
我不确定这是否真的是答案,但是评论太久了。
我认为快速的第一查询后跟缓慢的第二查询通常表示a statistics/cardinality feedback issue。
基本上,第一次运行查询时,优化器可能会检测到当前表/索引统计信息的估计基数(行数)非常不准确,因此它尝试为下一次运行查询缓存更准确的统计信息相同的查询。但这有时实际上会使情况变得更糟。
作为一种快速解决方案,AskTom suggests可以尝试使用/*+ opt_param('_optimizer_use_feedback' 'false') */
提示禁用该功能,或者使用SQL计划管理来保存好的计划,如上面的Ted所述。
从长远来看,我认为这可能表明您的某些统计数据过时了?您可以通过执行cardinality tuning并在计划the actual rows are a lot higher than the expected rows中查找位置来缩小问题统计范围。基本过程是使用/*+ GATHER_PLAN_STATISTICS */
提示运行查询,然后执行SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR(FORMAT=>'ALLSTATS LAST'));
来查看结果。