Запрос выполняется быстро в Oracle SQL Developer, но медленно в SSRS 2008 R2

Это так просто: запрос, который выполняется всего за несколько секунд в SQL Developer, подключающемся к Oracle 11g, занимает 15-25 минут в SSRS 2008 R2. Я не пробовал другие версии SSRS. До сих пор я выполняю все выполнение отчета из VS 2008.

Я использую поставщика OLE DB "OraOLEDB.Oracle.1", который в прошлом, казалось, дал мне лучшие результаты, чем использование поставщика Oracle.

Вот что я смог определить до сих пор:

• Задержка происходит во время фазы выполнения DataSet и не имеет никакого отношения к набору результатов или времени рендеринга. (Доказательство, выбрав тот же набор строк непосредственно из таблицы, в которую я вставил ее.)

• Сам SSRS не зависает. Это действительно ждет Oracle, где есть задержка (доказана завершением сеанса БД со стороны Oracle, что привело к быстрой ошибке в SSRS о том, что сеанс будет убит).

• Я пробовал прямые запросы с параметрами в форме: Параметр. Очень ранние версии моего запроса, которые были более простыми, работали нормально для прямого запроса, но, похоже, это была какая-то определенная сложность, запрос начнется навсегда из SSRS.

• Затем я переключился на выполнение SP, который вставляет результаты запроса в таблицу или глобальную таблицу temp. Это помогло немного, заставив меня дальше, чем прямое запрос, но опять же, похоже, что увеличение сложности запроса или длины в конечном итоге также нарушили этот метод. Примечание. Выполнение SP, работающего по таблице, работает, потому что с опцией "использовать одну транзакцию", отмеченной в параметрах DataSource, DataSets затем выполняются в порядке их появления в файле rdl. DataSets, которые не возвращают поля, все еще выполняются, если все их параметры выполнены.

• Я просто попробовал функцию возврата в таблицу, и это все еще не улучшилось, даже если прямые вызовы с литеральными параметрами в SQL Developer возвращаются через 1-5 секунд.

• В данной базе данных нет статистики. Это часть продукта, созданного поставщиком, и у нас не было времени или управления бай-ином для создания/обновления статистики. Я играл с подсказкой DYNAMIC_SAMPLING для расчета статистики на лету и получил лучший план выполнения: без статистики оптимизатор на основе затрат плохо использовал соединение LOOP вместо соединения HASH, вызывая подобные многоминутные времена выполнения. Таким образом, я помещал подсказки для запроса, чтобы заставить порядок соединения, а также заставить его использовать стратегическое хеш-соединение, в результате чего время выполнения возвращается к нескольким секундам. Я не вернулся и попробовал прямое запрос в SSRS с помощью этих подсказок.

• Я получил некоторую помощь от нашего Oracle DBA, который настроил трассировку (или независимо от эквивалента Oracle), и он смог увидеть, что все работает, но пока что он ничего не нашел. К сожалению, его время ограничено, и мы не смогли вникнуть, чтобы узнать, что выполняется на стороне сервера. У меня нет опыта, чтобы сделать это быстро или время, чтобы изучить, как это сделать сам. Предложения о том, что делать, чтобы определить, что происходит, будут оценены.

Мои единственные гипотезы:

• Запрос как-то получает плохой план выполнения. Например, неправильно использовать соединение LOOP вместо соединения HASH, когда есть десятки тысяч "левых" или внешних строк цикла, а не только несколько сотен.

• SSRS может отправлять параметры как nvarchar (4000) или что-то вместо чего-то разумного, и поскольку параметры Oracle SP и функции не имеют спецификаций длины, но получают длину выполнения из запроса запроса, тогда некоторый процесс, такой как параметр sniffing испортил план выполнения, как в предыдущей точке.

• Запрос как-то переписывается SSRS/поставщиком. Я использую многозначный параметр, но не как есть: параметр представляется как выражение Join (Parameters! MultiValuedParameter.Value, "," ), поэтому ему не нужно переписывать. Просто простая привязка и отправка. Я не вижу, как это может быть правдой в SP и вызовах функций, но gosh, что еще может быть?

Я понимаю, что это очень сложный и длительный запрос, но он делает именно то, что мне нужно. Он работает через 1-5 секунд в зависимости от того, сколько данных запрашивается. Некоторые из причин сложности:

  • Правильная обработка параметра списка МВЗ с разделителями-запятыми
  • Разрешить недельную разбивку быть необязательной и, если она включена, показывать все недели в месяц, даже если для них нет данных.
  • Показывать "Нет счетов-фактур", если это необходимо.
  • Разрешить переменное количество суммарных месяцев.
  • Наличие дополнительной суммы с начала года.
  • Включение предыдущих/исторических данных сравнения означает, что я не могу просто использовать продавцов этого месяца, я должен показать всех поставщиков, которые будут в любом историческом столбце.

В любом случае, так вот запрос, версия SP (хотя я не думаю, что это будет очень полезно).

create or replace
PROCEDURE VendorInvoiceSummary (
   FromDate IN date,
   ToDate IN date,
   CostCenterList IN varchar2,
   IncludeWeekly IN varchar2,
   ComparisonMonths IN number,
   IncludeYTD IN varchar2
)
AS
BEGIN

INSERT INTO InvoiceSummary (Mo, CostCenter, Vendor, VendorName, Section, TimeUnit, Amt)
SELECT
   Mo,
   CostCenter,
   Vendor,
   VendorName,
   Section,
   TimeUnit,
   Amt
FROM (
   WITH CostCenters AS (
      SELECT Substr(REGEXP_SUBSTR(CostCenterList, '[^,]+', 1, LEVEL) || '               ', 1, 15) CostCenter
      FROM DUAL
      CONNECT BY LEVEL <= Length(CostCenterList) - Length(Replace(CostCenterList, ',', '')) + 1
   ), Invoices AS (
      SELECT  /*+ORDERED USE_HASH(D)*/
         TRUNC(I.Invoice_Dte, 'YYYY') Yr,
         TRUNC(I.Invoice_Dte, 'MM') Mo,
         D.Dis_Acct_Unit CostCenter,
         I.Vendor,
         V.Vendor_VName,
         CASE
            WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate
            THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM')) / 7 + 1
            ELSE 0
         END WkNum,
         Sum(D.To_Base_Amt) To_Base_Amt
      FROM
         ICCompany C
         INNER JOIN APInvoice I
            ON C.Company = I.Company
         INNER JOIN APDistrib D
            ON C.Company = D.Company
            AND I.Invoice = D.Invoice
            AND I.Vendor = D.Vendor
            AND I.Suffix = D.Suffix
         INNER JOIN CostCenters CC
            ON D.Dis_Acct_Unit = CC.CostCenter
         INNER JOIN APVenMast V ON I.Vendor = V.Vendor
      WHERE
         D.Cancel_Seq = 0
         AND I.Cancel_Seq = 0
         AND I.Invoice_Dte >= Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY'))
         AND I.Invoice_Dte < ToDate
         AND V.Vendor_Group = '1   ' -- index help
      GROUP BY
         TRUNC(I.Invoice_Dte, 'YYYY'),
         TRUNC(I.Invoice_Dte, 'MM'),
         D.Dis_Acct_Unit,
         I.Vendor,
         V.Vendor_VName,
         CASE
            WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate
            THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM')) / 7 + 1
            ELSE 0
         END
   ), Months AS (
      SELECT ADD_MONTHS(Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')), LEVEL - 1) Mo
      FROM DUAL
      CONNECT BY LEVEL <= MONTHS_BETWEEN(ToDate, Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')))
   ), Sections AS (
      SELECT 1 Section, 1 StartUnit, 5 EndUnit FROM DUAL
      UNION ALL SELECT 2, 0, ComparisonMonths FROM DUAL
      UNION ALL SELECT 3, 1, 1 FROM DUAL WHERE IncludeYTD = 'Y'
   ), Vals AS (
      SELECT LEVEL - 1 TimeUnit
      FROM DUAL
      CONNECT BY LEVEL <= (SELECT Max(EndUnit) FROM Sections) + 1
   ), TimeUnits AS (
      SELECT S.Section, V.TimeUnit
      FROM
         Sections S
         INNER JOIN Vals V
            ON V.TimeUnit BETWEEN S.StartUnit AND S.EndUnit
   ), Names AS (
      SELECT DISTINCT
         M.Mo,
         Coalesce(I.Vendor, '0') Vendor,
         Coalesce(I.Vendor_VName, 'No Paid Invoices') Vendor_VName,
         Coalesce(I.CostCenter, ' ') CostCenter
      FROM
         Months M
         LEFT JOIN Invoices I
            ON Least(ADD_MONTHS(M.Mo, -ComparisonMonths), TRUNC(M.Mo, 'YYYY')) < I.Mo
            AND M.Mo >= I.Mo
      WHERE
         M.Mo >= FromDate
         AND M.Mo < ToDate
   )
   SELECT
      N.Mo,
      N.CostCenter,
      N.Vendor,
      N.Vendor_VName VendorName,
      T.Section,
      T.TimeUnit,
      Sum(I.To_Base_Amt) Amt
   FROM
      Names N
      CROSS JOIN TimeUnits T
      LEFT JOIN Invoices I
         ON N.CostCenter = I.CostCenter
         AND N.Vendor = I.Vendor
         AND (
            (
               T.Section = 1 -- Weeks for current month
               AND N.Mo = I.Mo
               AND T.TimeUnit = I.WkNum
            ) OR (
               T.Section = 2 -- Summary months
               AND ADD_MONTHS(N.Mo, -T.TimeUnit) = I.Mo
            ) OR (
               T.Section = 3 -- YTD
               AND I.Mo BETWEEN TRUNC(N.Mo, 'YYYY') AND N.Mo
            )
         )
   WHERE
      N.Mo >= FromDate
      AND N.Mo < ToDate
      AND NOT ( -- Only 4 weeks when a month is less than 28 days long
         T.Section = 2
         AND T.TimeUnit = 5
         AND TRUNC(N.Mo + 28, 'MM') <> N.Mo
         AND I.CostCenter IS NULL
      ) AND (
         T.Section <> 1
         OR IncludeWeekly = 'Y'
      )
   GROUP BY
      N.Mo,
      N.CostCenter,
      N.Vendor,
      N.Vendor_VName,
      T.Section,
      T.TimeUnit
) X;
COMMIT;
END;

UPDATE

Даже после того, как вы узнали все о планах и подсказках Oracle о выполнении (чтобы перевести мои знания SQL Server), я все еще не мог получить запрос для быстрого запуска в SSRS, пока я не запустил его в два этапа, сначала чтобы поместить результаты реальной таблицы в GLOBAL TEMPORARY TABLE, а затем второй, чтобы извлечь данные из этого. DYNAMIC_SAMPLING дал мне хороший план выполнения, который я затем скопировал с помощью советов о соединении и доступе. Здесь последний SP (это не может быть функцией, потому что в Oracle вы не можете выполнять DML в функции, когда эта функция вызывается внутри оператора SELECT):

Иногда я клянусь, что игнорировал мои намеки на соединение, такие как swap_join_inputs и no_swap_join_inputs, но из моего чтения очевидно, что Oracle игнорирует подсказки, когда они не могут фактически использоваться, или вы делаете что-то неправильно. К счастью, таблицы соответствующим образом менялись местами (как в случае USE_NL(CC), он надежно кладет таблицу CC как замененный, левый вход, хотя он и соединяется последним).

CREATE OR REPLACE
PROCEDURE VendorInvoicesSummary (
   FromDate IN date,
   ToDate IN date,
   CostCenterList IN varchar2,
   IncludeWeekly IN varchar2,
   ComparisonMonths IN number,
   IncludeYTD IN varchar2
)
AS
BEGIN

INSERT INTO InvoiceTemp (Yr, Mo, CostCenter, Vendor, WkNum, Amt) -- A global temporary table
SELECT /*+LEADING(C I D CC) USE_HASH(I D) USE_NL(CC)*/
   TRUNC(I.Invoice_Dte, 'YYYY') Yr,
   TRUNC(I.Invoice_Dte, 'MM') Mo,
   D.Dis_Acct_Unit CostCenter,
   I.Vendor,
   CASE
      WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate
      THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM')) / 7 + 1
      ELSE 0
   END WkNum,
   Sum(D.To_Base_Amt) To_Base_Amt
FROM
   ICCompany C
   INNER JOIN APInvoice I
      ON C.Company = I.Company
   INNER JOIN APDistrib D
      ON C.Company = D.Company
      AND I.Invoice = D.Invoice
      AND I.Vendor = D.Vendor
      AND I.Suffix = D.Suffix
   INNER JOIN (
      SELECT Substr(REGEXP_SUBSTR(CostCenterList, '[^,]+', 1, LEVEL) || '               ', 1, 15) CostCenter
      FROM DUAL
      CONNECT BY LEVEL <= Length(CostCenterList) - Length(Replace(CostCenterList, ',', '')) + 1
   ) CC ON D.Dis_Acct_Unit = CC.CostCenter
WHERE
   D.Cancel_Seq = 0
   AND I.Cancel_Seq = 0
   AND I.Invoice_Dte >= Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY'))
   AND I.Invoice_Dte < ToDate
GROUP BY
   TRUNC(I.Invoice_Dte, 'YYYY'),
   TRUNC(I.Invoice_Dte, 'MM'),
   D.Dis_Acct_Unit,
   I.Vendor,
   CASE
      WHEN I.Invoice_Dte >= FromDate AND I.Invoice_Dte < ToDate
      THEN (TRUNC(I.Invoice_Dte, 'W') - TRUNC(I.Invoice_Dte, 'MM')) / 7 + 1
      ELSE 0
   END;

INSERT INTO InvoiceSummary (Mo, CostCenter, Vendor, VendorName, Section, TimeUnit, Amt)
SELECT
   Mo,
   CostCenter,
   Vendor,
   VendorName,
   Section,
   TimeUnit,
   Amt
FROM (
   WITH Months AS (
      SELECT ADD_MONTHS(Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')), LEVEL - 1) Mo
      FROM DUAL
      CONNECT BY LEVEL <= MONTHS_BETWEEN(ToDate, Least(ADD_MONTHS(FromDate, -ComparisonMonths), TRUNC(FromDate, 'YYYY')))
   ), Sections AS (
      SELECT 1 Section, 1 StartUnit, 5 EndUnit FROM DUAL
      UNION ALL SELECT 2, 0, ComparisonMonths FROM DUAL
      UNION ALL SELECT 3, 1, 1 FROM DUAL WHERE IncludeYTD = 'Y'
   ), Vals AS (
      SELECT LEVEL - 1 TimeUnit
      FROM DUAL
      CONNECT BY LEVEL <= (SELECT Max(EndUnit) FROM Sections) + 1
   ), TimeUnits AS (
      SELECT S.Section, V.TimeUnit
      FROM
         Sections S
         INNER JOIN Vals V
            ON V.TimeUnit BETWEEN S.StartUnit AND S.EndUnit
   ), Names AS (
      SELECT DISTINCT
         M.Mo,
         Coalesce(I.Vendor, '0') Vendor,
         Coalesce(I.CostCenter, ' ') CostCenter
      FROM
         Months M
         LEFT JOIN InvoiceTemp I
            ON Least(ADD_MONTHS(M.Mo, -ComparisonMonths), TRUNC(M.Mo, 'YYYY')) <= I.Mo
            AND I.Mo <= M.Mo
      WHERE
         M.Mo >= FromDate
         AND M.Mo < ToDate
   )
   SELECT
      N.Mo,
      N.CostCenter,
      N.Vendor,
      Coalesce(V.Vendor_VName, 'No Paid Invoices') VendorName,
      T.Section,
      T.TimeUnit,
      Sum(I.Amt) Amt
   FROM
      Names N
      INNER JOIN APVenMast V ON N.Vendor = V.Vendor
      CROSS JOIN TimeUnits T
      LEFT JOIN InvoiceTemp I
         ON N.CostCenter = I.CostCenter
         AND N.Vendor = I.Vendor
         AND (
            (
               T.Section = 1 -- Weeks for current month
               AND N.Mo = I.Mo
               AND T.TimeUnit = I.WkNum
            ) OR (
               T.Section = 2 -- Summary months
               AND ADD_MONTHS(N.Mo, -T.TimeUnit) = I.Mo
            ) OR (
               T.Section = 3 -- YTD
               AND I.Mo BETWEEN TRUNC(N.Mo, 'YYYY') AND N.Mo
            )
         )
   WHERE
      N.Mo >= FromDate
      AND N.Mo < ToDate
      AND V.Vendor_Group = '1   '
      AND NOT ( -- Only 4 weeks when a month is less than 28 days long
         T.Section = 2
         AND T.TimeUnit = 5
         AND TRUNC(N.Mo + 28, 'MM') <> N.Mo
         AND I.CostCenter IS NULL
      ) AND (
         T.Section <> 1
         OR IncludeWeekly = 'Y'
      )
   GROUP BY
      N.Mo,
      N.CostCenter,
      N.Vendor,
      V.Vendor_VName,
      T.Section,
      T.TimeUnit
) X;

COMMIT;
END;

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

Ответ 1

Проводка запроса может помочь.

Ваш администратор базы данных должен иметь возможность идентифицировать сеанс в представлении с именем v $session, а столбцы EVENT и WAIT_CLASS должны указывать на то, что происходит на стороне Oracle.

Он также сможет идентифицировать SQL (SQL_ID из v $session) и использовать его в SELECT * FROM TABLE (DBMS_XPLAN.DISPLAY_CURSOR (sql_id)), чтобы определить план.

Если это экземпляр разработки/теста, посмотрите, предоставит ли он вам разрешения сделать это самостоятельно, если он (или она) занят.

Ответ 2

Я знаю, что это старо, но у нас была аналогичная проблема, и мне нужно было установить nsl_sort в двоичный файл вместо binary_ci. Люди могут попробовать настроить сеанс на двоичный: изменить набор сеансов nls_sort = двоичный