Запрос выполняется быстро в Oracle SQL Developer, но медленно в SSRS 2008 R2
это так просто: запрос, который выполняется всего за несколько секунд в SQL Developer подключение к Oracle 11g занимает 15-25 минут в SSRS 2008 R2. Я не пробовал другие версии SSRS. Пока я делаю все выполнение отчета с VS 2008.
Я использую поставщик OLE DB " OraOLEDB.Оракул.1 " который в прошлом, казалось, дал мне лучшие результаты, чем использование поставщика Oracle.
вот что я смог определить так далеко:
• задержка происходит на этапе выполнения набора данных и не имеет ничего общего с результирующим набором или временем рендеринга. (Доказательство, выбрав тот же набор строк непосредственно из таблицы, в которую я его вставил.)
* SSRS сам не вешается. Он действительно ждет Oracle, где задержка (доказано, завершив сеанс DB со стороны Oracle, что привело к быстрой ошибке в SSRS о том, что сеанс был убит).
• Я старался прямые запросы с параметрами в виде :параметр. Очень ранние версии моего запроса, которые были более простыми, хорошо работали для прямого запроса, но казалось, что после определенной сложности запрос начнет принимать навсегда от SSRS.
• затем я переключился на выполнение SP, который вставляет результаты запроса в таблицу или глобальную временную таблицу. Это помогло на некоторое время, получив меня дальше, чем прямой запрос, но опять же, это почти похоже на увеличение сложности или длины запроса в конце концов этот метод тоже был нарушен. Примечание: запуск заполняющего таблицу SP работает, потому что с опцией "использовать одну транзакцию", отмеченной в параметрах источника данных, наборы данных затем запускаются в порядке их появления в файле rdl. Наборы данных, которые не возвращают полей, по-прежнему выполняются, пока все их параметры удовлетворены.
• Я только что попробовал функцию возврата таблицы, и это все еще не улучшилось, хотя прямые вызовы с литеральными параметрами в SQL Developer возвращаются в 1-5 считанные секунды.
• база данных, о которой идет речь, не имеет статистики. Это часть продукта, созданного поставщиком, и у нас не было времени или управления покупкой для создания/обновления статистики. Я играл с подсказкой DYNAMIC_SAMPLING для расчета статистики на лету и получил лучший план выполнения: без статистики оптимизатор на основе затрат плохо использовал соединение цикла вместо хэш-соединения, вызывая аналогичные многоминутные времена выполнения. Таким образом, я помещаю в запрос подсказки для force join порядок, а также заставить его использовать стратегическое хэш-соединение, сократив время выполнения до нескольких секунд. Я не вернулся и не попытался выполнить прямой запрос в SSRS, используя эти подсказки выполнения.
• я получил некоторую помощь от нашего Oracle DBA, который установил трассировку (или что-то в эквиваленте Oracle), и он смог увидеть, что вещи запускаются, но он не нашел ничего полезного до сих пор. К сожалению, его время ограничено и мы не смогли взяться за дело, чтобы выяснить, что выполняется на стороне сервера. У меня нет опыта, чтобы сделать это быстро, или времени, чтобы изучить, как это сделать самому. Предложения о том, что делать, чтобы определить, что происходит, будут оценены.
мои единственные гипотезы:
• запрос каким-то образом получает плохой план выполнения. Например, неправильно использовать соединение цикла вместо хэш-соединения, когда есть десятки тысяч" левых " или внешних строк, а не только несколько сотен.
• SSRS может отправлять параметры как nvarchar (4000) или что-то вместо чего-то разумного, и поскольку параметры Oracle SP & function не имеют спецификаций длины, но получают их длины выполнения из вызова запроса, тогда некоторый процесс, такой как обнюхивание параметров, портит план выполнения, как и в предыдущей точке.
• запрос каким-то образом переписывается SSRS / поставщиком. Я использую многозначный параметр, но не как есть: параметр передается как выражение соединения(параметры!MultiValuedParameter.Value,","), поэтому он не должен нуждаться в переписывании. Просто простая привязка и подчинение. Я не вижу, как это может быть правдой в SP и вызовах функций, но, черт возьми, что еще это может быть?
Я понимаю, что это очень сложный и длинный запрос, но он делает именно то, что мне нужно. Он работает в 1-5 секунд, в зависимости от того, сколько данных просят. Некоторые из причин сложности:
- правильная обработка разделенный запятыми параметр списка центра затрат
- позволяя еженедельную разбивку быть необязательным и если включено, обеспечение всех недель в месяц показаны, даже если нет данных для них.
- отображение "нет счетов-фактур", когда это необходимо.
- разрешение переменного количества месяцев сводки.
- наличие дополнительного итога YTD.
- включая предыдущие / исторические данные сравнения означает, что я не могу просто использовать этой месяц поставщики, я должен показать всех поставщиков, которые будут в любой исторической колонке.
в любом случае, вот запрос, версия 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;
обновление
даже после изучения всех планов выполнения Oracle и подсказок (для перевода моих знаний SQL Server), я все еще не мог заставить запрос быстро работать в SSRS, пока я не сделал это в два шага, сначала, чтобы поместить реальные результаты таблицы в GLOBAL TEMPORARY TABLE
а потом второй, чтобы извлечь данные из этого. DYNAMIC_SAMPLING
дал мне хороший план выполнения, который я затем скопировал, используя подсказки join и access. Вот окончательный 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, даже если поставщик не заботится о них) может быть настоящей катастрофой для тех, кто хочет сделать все за разумное время.
2 ответов
публикация запроса может помочь.
ваш DBA должен иметь возможность идентифицировать сеанс в представлении под названием V$session, а событие columns и WAIT_CLASS должны давать указание на то, что происходит на конце Oracle.
Он также сможет идентифицировать SQL (SQL_ID из сеанса v$) и использовать его в таблице SELECT * FROM(DBMS_XPLAN.DISPLAY_CURSOR (sql_id)) для определения плана.
Если это экземпляр разработки / теста, посмотрите, предоставит ли он вам разрешения сделать это самостоятельно, если он (или она) занят.
Я знаю, что это старый, но у нас была аналогичная проблема, и нам пришлось установить nsl_sort в двоичный вместо binary_ci. Люди могли бы попробовать настройки сессии в двоичную: альтер сессии nls_sort=бинарные