Запрос Oracle выполняется только последовательно при добавлении случайного комментария - PullRequest
0 голосов
/ 04 февраля 2019

Редактировать: Решено!спасибо @ 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

Я запустил это, пока только что обновил данныев приложении, в результате чего запрос выполняется во второй раз.Я вижу это:

query results

Первый запрос прошел нормально, индексы в порядке.Во второй раз он запустил полное сканирование таблицы.Это не нужно, потому что работает нормально как в первый раз, так и в 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();
}

1 Ответ

0 голосов
/ 04 февраля 2019

Я не уверен, что это действительно ответ, но это слишком долго для комментария.

Я думаю, что быстрый первый запрос, сопровождаемый медленным вторым запросом, часто указывает обратная связь по статистике / количеству элементоввыпуск .

Обычно при первом запуске запроса оптимизатор может обнаружить, что оценочная мощность (количество строк) из текущей статистики таблицы / индекса довольно неточна, поэтому он пытается кэшировать более точную статистику для следующегозапустить тот же запрос.Но иногда это на самом деле делает все намного хуже.

В качестве быстрого исправления AskTom предлагает , что вы можете попробовать отключить эту функцию с помощью подсказки /*+ opt_param('_optimizer_use_feedback' 'false') */ или использовать SQL Plan Management для сохраненияхороший план, как упоминал Тед выше.

В долгосрочной перспективе, я думаю, это может указывать на то, что некоторые ваши статистические данные могут быть устаревшими?Вы можете сузить статистику проблем, выполнив кардинальную настройку и выяснив, где в плане фактические строки намного выше ожидаемых строк .Основной процесс - выполнить запрос с подсказкой /*+ GATHER_PLAN_STATISTICS */, а затем выполнить SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR(FORMAT=>'ALLSTATS LAST'));, чтобы увидеть результаты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...