查询在Oracle SQL Developer中快速运行,但在SSRS 2008 R2中运行缓慢

Eri*_*ikE 11 sql-server oracle execution-time oracle11g ssrs-2008

就是这么简单:在SQL Developer连接到Oracle 11g的几秒钟内运行的查询在SSRS 2008 R2中需要15-25分钟.我还没有尝试过其他版本的SSRS.到目前为止,我正在从VS 2008执行所有报告执行.

我正在使用OLE DB提供程序"OraOLEDB.Oracle.1",它在过去似乎比使用Oracle提供程序给我更好的结果.

这是我到目前为止所能确定的:

•延迟是在DataSet执行阶段,与结果集或渲染时间无关.(通过直接从我插入的表中选择相同的行集来证明.)

•SSRS本身并未挂断.它确实在等待延迟所在的Oracle(通过从Oracle端终止数据库会话来证明,这导致SSRS中关于被杀死的会话的快速错误).

•我尝试使用以下形式的参数进行直接查询:参数.我的查询的早期版本更简单,可以直接查询,但似乎过了一定的复杂性,查询将从SSRS开始永远.

•然后我切换到执行将查询结果插入表或全局临时表的SP.这有点帮助,让我比直接查询更远,但同样,似乎增加的查询复杂性或长度最终也破坏了这种方法.注意:运行填充表的SP是可行的,因为在DataSource选项中选中了"使用单个事务"选项,然后按照它们在rdl文件中的出现顺序运行DataSet.只要满足所有参数,仍然会运行不返回Fields的DataSet.

•我刚刚尝试了一个表返回函数,这仍然没有改进,即使在SQL Developer中使用文字参数的直接调用在1-5秒内返回.

•有问题的数据库没有统计数据.它是供应商创建的产品的一部分,我们没有时间或管理层支持来创建/更新统计数据.我使用DYNAMIC_SAMPLING提示来动态计算统计数据并获得更好的执行计划:没有统计数据,基于成本的优化器很少使用LOOP连接而不是HASH连接,导致类似的多分钟执行时间.因此,我提出了查询提示来强制连接顺序,并使其使用策略散列连接,将执行时间缩短到几秒钟.我没有回去尝试使用这些执行提示直接在SSRS中查询.

•我从我们的Oracle DBA那里得到了一些帮助,他们设置了一个跟踪(或者等同于Oracle的东西)并且他能够看到正在运行的东西,但到目前为止他还没有找到任何有用的东西.不幸的是,他的时间有限,我们无法真正深入了解服务器端的执行情况.我没有经验可以快速完成这项工作,也没有时间自己研究如何做到这一点.关于如何确定发生了什么的建议将不胜感激.

我唯一的假设是:

•查询以某种方式获得了糟糕的执行计划.例如,当存在数万个"左"或外环行而不是仅仅几百个时,不正确地使用LOOP连接而不是HASH连接.

•SSRS可以将参数提交为nvarchar(4000)或其他东西而不是合理的东西,并且由于Oracle SP和函数参数没有长度规范但是从查询调用中获取它们的执行长度,那么参数嗅探等一些过程就是弄乱执行计划,就像前一点一样.

•查询以某种方式被SSRS /提供者重写.我使用多值参数,但不是这样:参数作为表达式Join(参数!MultiValuedParameter.Value,",")提交,因此它不需要任何重写.只是一个简单的绑定和提交.我不知道在SP和函数调用中这是怎么回事,但天哪,还有什么呢?

我意识到这是一个非常复杂和冗长的查询,但它完全符合我的需要.它会在1-5秒内运行,具体取决于要求的数据量.造成这种复杂性的一些原因是:

  • 正确处理逗号分隔的成本中心列表参数
  • 允许每周细分是可选的,如果包括在内,即使没有数据,也可以确保一个月内的所有周数.
  • 适当时显示"No Invoices".
  • 允许可变数量的汇总月份.
  • 有一个可选的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;
Run Code Online (Sandbox Code Playgroud)

UPDATE

即使在了解了所有关于Oracle执行计划和提示(以翻译我的SQL Server知识)之后,我仍然无法让查询在SSRS中快速运行,直到我分两步运行,首先将实际表结果放入a GLOBAL TEMPORARY TABLE和然后第二个从中提取数据.DYNAMIC_SAMPLING给了我一个很好的执行计划,然后我使用join和access提示进行复制.这是最终的SP(它不能是一个函数,因为在Oracle中,当在SELECT语句中调用该函数时,你不能在函数中执行DML):

有时候我发誓它忽略了我的联接提示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;
Run Code Online (Sandbox Code Playgroud)

这是一个漫长而痛苦的旅程,但是如果有一件事我已经知道它是在数据库中工作而没有正确更新的统计数据(我将考虑让我们的DBA添加,即使供应商不关心关于他们)对于想要在合理的时间内完成工作的人来说,这可能是一场真正的灾难.

Gar*_*ers 1

发布查询可能会有所帮助。

您的 DBA 应该能够在名为 v$session 的视图中识别会话,并且 EVENT 和 WAIT_CLASS 列应该指示 Oracle 端发生的情况。

他还能够识别 SQL(来自 v$session 的 SQL_ID)并在 SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(sql_id)) 中使用它来确定计划。

如果是开发/测试实例,看看他(或她)很忙时是否会授予您自己执行此操作的权限。