Редактировать: Решено!спасибо @ kfinity.
AskTom предлагает использовать select / * + opt_param ('_ optimizer_use_feedback' 'false') * / в начале вашего запроса, чтобы отключить использование обратной связи.Это устранило проблему для меня.
tldr: добавление рандомизированного комментария к запросу делает его согласованным, удаление этого комментария нарушает его.
Предупреждение: long.
Среда
Мой предпочтительный способ работы с запросами в источнике в виде строки, чтобы они находились в управлении версиями, и я могу видеть изменения с течением времени.Наряду с этим я использую dapper
и пакет oracle.ManagedDataAccess
NuGet.Рассматриваемое приложение - это приложение WPF (в обоих случаях), работающее на платформе .NET 4.7.2.Я использую Visual Studio Professional 2017 15.9.5.
Проблема
Около года назад я столкнулся с этой проблемой с помощью запроса.Я не помню, что это было, я знаю, что у меня не было времени документировать это и публиковать здесь.Я делаю сейчас, и я столкнулся с той же проблемой.Тогда я как-то понял, что если я перезагружу свой компьютер или изменю текст запроса, он снова будет работать нормально.Просто иногда появлялись симптомы проблемы, я добавлял комментарий к запросу или удалял предыдущий, я проверял эту конкретную функцию в каждом выпуске.Я проверял бы это каждый раз, потому что, если бы он был неисправен на моей машине, он также был бы неисправен на целевой машине пользователя.В то время я полагал, что это была проблема с драйверами / оборудованием на моем компьютере.Я понял, что могу исправить это, изменив текст запроса, потому что я вырезал и вставлял (ctrl-x ctrl-v
) весь запрос из кода в Oracle developer
и редактировал его там.В какой-то момент я заметил, что даже дополнительный пробел или ввод заставят его работать снова.
Вернемся к этой проблеме, у меня снова возникла проблема.На этот раз все по-другому, потому что время от времени это не терпит неудачу.Это очень последовательно.Вспоминая, как я решил, что это проблема с драйверами и оборудованием, я создал и запустил приложение на 3 разных машинах, и результаты были одинаковыми.Я могу выполнить запрос в Oracle developer
, используя ctrl + end
для выполнения всего запроса, а не только 50 строк.Это работает около 10 секунд.Он работает последовательно, снова и снова, что имеет смысл, если ничего не меняется.
Если я запускаю его из своего приложения, оно работает нормально - но только один раз.Это также занимает около 10 секунд.Если я обновляю данные, которые снова запускают запрос, он зависает.Нет никаких исключений, если я отключаю отладчик, он просто охлаждает вызов database.Query<>()
.Если я перезагружу компьютер или меняю запрос, он будет выполнен - ровно один раз.
v $ session_longops / full table scan
Конечно, я обратился за помощью в Google.Нашел несколько интересных статей, которые мне не очень помогли:
https://mjsoracleblog.wordpress.com/2014/10/24/oracle-performance-mystery-wildly-varying-query-response-time/
Oracle несовместимо с производительностью запроса
Пока я не нашел это:
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
.Это дало мне два совершенно разных плана, тот, что в Oracle developer
, всегда имеет эту заметку: - 'PLAN_TABLE' is old version
Поэтому я не уверен, что могу доверять этой информации, я не знаю, что с ней делать.
План от разработчика Oracle
Планиз кода
Исправление
Как я уже говорил, добавление или удаление комментария или, скорее, изменение текста запроса устраняет проблему и никогда не запускает полное сканирование таблицы.Я добавил комментарий, содержащий 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();
}