简体   繁体   English

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

[英]Query runs quickly in Oracle SQL Developer, but slowly in SSRS 2008 R2

It's that simple: a query that runs in just a few seconds in SQL Developer connecting to Oracle 11g takes 15-25 minutes in SSRS 2008 R2. 就是这么简单:在SQL Developer连接到Oracle 11g的几秒钟内运行的查询在SSRS 2008 R2中需要15-25分钟。 I haven't tried other versions of SSRS. 我还没有尝试过其他版本的SSRS。 So far I'm doing all the report execution from VS 2008. 到目前为止,我正在从VS 2008执行所有报告执行。

I'm using the OLE DB Provider "OraOLEDB.Oracle.1" which in the past has seemed to give me better results than using the Oracle provider. 我正在使用OLE DB提供程序“OraOLEDB.Oracle.1”,它在过去似乎比使用Oracle提供程序给我更好的结果。

Here's what I've been able to determine so far: 这是我到目前为止所能确定的:

• The delay is during the DataSet execution phase and has nothing to do with the result set or rendering time. •延迟是在DataSet执行阶段,与结果集或渲染时间无关。 (Proving by selecting the same rowset directly from a table I inserted it to.) (通过直接从我插入的表中选择相同的行集来证明。)

• SSRS itself is not hung up. •SSRS本身并未挂断。 It is truly waiting for Oracle which is where the delay is (proven by terminating the DB session from the Oracle side, which resulted in a prompt error in SSRS about the session being killed). 它确实在等待延迟所在的Oracle(通过从Oracle端终止数据库会话来证明,这导致SSRS中关于被杀死的会话的快速错误)。

• I have tried direct queries with parameters in the form :Parameter. •我尝试使用以下形式的参数进行直接查询:参数。 Very early versions of my query that were more simple worked okay for direct querying, but it seemed like past a certain complexity, the query would start taking forever from SSRS. 我的查询的早期版本更简单,可以直接查询,但似乎过了一定的复杂性,查询将从SSRS开始永远。

• I then switched to executing an SP that inserts the query results to a table or global temp table. •然后我切换到执行将查询结果插入表或全局临时表的SP。 This helped for a little while, getting me farther than direct querying, but again, it almost seems like increased query complexity or length eventually broke this method, too. 这有点帮助,让我比直接查询更远,但同样,似乎增加的查询复杂性或长度最终也破坏了这种方法。 Note: running a table-populating SP works because with option "use single transaction" checked in the DataSource options, DataSets are then run in the order of their appearance in the rdl file. 注意:运行填充表的SP是可行的,因为在DataSource选项中选中了“使用单个事务”选项,然后按照它们在rdl文件中的出现顺序运行DataSet。 DataSets that return no Fields are still run, as long as all their parameters are satisfied. 只要满足所有参数,仍然会运行不返回Fields的DataSet。

• I just tried a table-returning function and this still made no improvement, even though direct calls with literal parameters in SQL Developer return in 1-5 seconds. •我刚刚尝试了一个表返回函数,这仍然没有改进,即使在SQL Developer中使用文字参数的直接调用在1-5秒内返回。

• The database in question does not have statistics. •有问题的数据库没有统计数据。 It is part of a product created by a vendor and we have not had the time or management buy-in to create/update statistics. 它是供应商创建的产品的一部分,我们没有时间或管理层支持来创建/更新统计数据。 I played with the DYNAMIC_SAMPLING hint to calculate statistics on the fly and got a better execution plan: without statistics the cost-based optimizer had been poorly using a LOOP join instead of a HASH join, causing similar many-minute execution times. 我使用DYNAMIC_SAMPLING提示来动态计算统计数据并获得更好的执行计划:没有统计数据,基于成本的优化器很少使用LOOP连接而不是HASH连接,导致类似的多分钟执行时间。 Thus I put in query hints to force join order and also to cause it to use the strategic hash join, bringing the execution time back down to just seconds. 因此,我提出了查询提示来强制连接顺序,并使其使用策略散列连接,将执行时间缩短到几秒钟。 I did not go back and try direct querying in SSRS using these execution hints. 我没有回去尝试使用这些执行提示直接在SSRS中查询。

• I got some help from our Oracle DBA who set up a trace (or whatever the Oracle equivalent is) and he was able to see things being run, but he hasn't found anything useful so far. •我从我们的Oracle DBA那里获得了一些帮助,他们设置了一个跟踪(或者等同于Oracle的任何东西)并且他能够看到正在运行的东西,但他到目前为止还没有找到任何有用的东西。 Unfortunately his time is limited and we haven't been able to really dig in to find out what's being executed server-side. 不幸的是,他的时间有限,我们无法真正深入了解服务器端的执行情况。 I don't have the experience to do this quickly or the time to study up on how to do this myself. 我没有经验可以快速完成这项工作,也没有时间自己研究如何做到这一点。 Suggestions on what to do to determine what's going on would be appreciated. 关于如何做以确定发生了什么的建议将不胜感激。

My only hypotheses are: 我唯一的假设是:

• The query is somehow getting a bad execution plan. •查询以某种方式获得了糟糕的执行计划。 Eg, improperly using a LOOP join instead of a HASH join when there are tens of thousands of "left" or outer-loop rows rather than just a few hundred. 例如,当存在数万个“左”或外环行而不是仅仅几百个时,不正确地使用LOOP连接而不是HASH连接。

• SSRS could be submitting the parameters as nvarchar(4000) or something instead of something reasonable, and as Oracle SP & function parameters don't have length specifications but get their execution lengths from the query call, then some process such as parameter sniffing is messing up the execution plan as in the previous point. •SSRS可以将参数提交为nvarchar(4000)或其他东西而不是合理的东西,并且由于Oracle SP和函数参数没有长度规范但是从查询调用中获取它们的执行长度,那么参数嗅探等一些过程就是弄乱执行计划,就像前一点一样。

• The query is somehow being rewritten by SSRS/the provider. •查询以某种方式被SSRS /提供者重写。 I AM using a multi-valued parameter, but not as is: the parameter is being submitted as expression Join(Parameters!MultiValuedParameter.Value, ",") so it shouldn't need any rewriting. 我使用多值参数,但不是这样:参数作为表达式Join(参数!MultiValuedParameter.Value,“,”)提交,因此它不需要任何重写。 Just a simple binding and submitting. 只是一个简单的绑定和提交。 I don't see how this could be true in the SP and the function calls, but gosh, what else could it be? 我不知道在SP和函数调用中这是怎么回事,但天哪,还有什么呢?

I realize it is a very complicated and lengthy query, but it does exactly what I need. 我意识到这是一个非常复杂和冗长的查询,但它完全符合我的需要。 It runs in 1-5 seconds depending on how much data is asked for. 它会在1-5秒内运行,具体取决于要求的数据量。 Some of the reasons for the complexity are: 造成这种复杂性的一些原因是:

  • Properly handling the comma-separated cost center list parameter 正确处理逗号分隔的成本中心列表参数
  • Allowing the weekly breakdown to be optional and if included, ensuring all the weeks in a month are shown even if there is no data for them. 允许每周细分是可选的,如果包括在内,即使没有数据,也可以确保一个月内的所有周数。
  • Showing "No Invoices" when appropriate. 适当时显示“No Invoices”。
  • Allowing a variable number of summary months. 允许可变数量的汇总月份。
  • Having an optional YTD total. 有一个可选的YTD总计。
  • Including previous/historical comparison data means I can't simply use this month's vendors, I have to show all the vendors that will be in any historical column. 包括以前/历史比较数据意味着我不能简单地用这个月的销售商,我要显示所有的供应商,将在任何历史列。

Anyway, so here's the query, SP version (though I don't think it will be much help). 无论如何,所以这里是查询,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 UPDATE

Even after learning all about Oracle execution plans and hints (to translate my SQL Server knowledge), I still could not get the query to run quickly in SSRS until I made it run in two steps, first to put the real table results into a GLOBAL TEMPORARY TABLE and then second to extract the data from that. 即使在了解了所有关于Oracle执行计划和提示(以翻译我的SQL Server知识)之后,我仍然无法让查询在SSRS中快速运行,直到我分两步运行,首先将实际表结果放入GLOBAL TEMPORARY TABLE然后第二个从中提取数据。 DYNAMIC_SAMPLING gave me a good execution plan, which I then copied using join and access hints. DYNAMIC_SAMPLING给了我一个很好的执行计划,然后我使用join和access提示进行复制。 Here's the final SP (it couldn't be a function because in Oracle you can't do DML in a function when that function is called inside of a SELECT statement): 这是最终的SP(它不能是一个函数,因为在Oracle中,当在SELECT语句中调用该函数时,你不能在函数中执行DML):

Sometimes I swear it was ignoring my join hints such as swap_join_inputs and no_swap_join_inputs but from my reading apparently Oracle only ignores hints when they can't actually be used or you're doing something wrong. 有时候我发誓它忽略了我的连接提示,例如swap_join_inputsno_swap_join_inputs但是从我的阅读中可以看出,甲骨文只有当它们实际上无法使用或者你做错了时才会忽略提示。 Fortunately, the tables swapped appropriately (as in the case of USE_NL(CC) it reliably puts the CC table as the swapped, left input, even though it's joined last). 幸运的是,这些表被适当地交换了(就像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;

This has been a long, painful ride, but if there's one thing I've learned it's that working in a database without properly updated statistics (which I'm going to look into getting our DBA to add even though the vendor doesn't care about them) can be a real disaster for someone who wants to get things done in a reasonable amount of time. 这是一个漫长而痛苦的旅程,但是如果有一件事我已经知道它是在数据库中工作而没有正确更新的统计数据(我将考虑让我们的DBA添加,即使供应商不关心关于他们)对于想要在合理的时间内完成工作的人来说,这可能是一场真正的灾难。

I know this is old but we had a similar problem and had to set the nsl_sort to binary instead of binary_ci. 我知道这是旧的,但我们有类似的问题,不得不将nsl_sort设置为二进制而不是binary_ci。 People could try setting the session to binary: alter session set nls_sort=binary 人们可以尝试将会话设置为二进制:alter session set nls_sort = binary

Posting the query may help. 发布查询可能有所帮助。

Your DBA should be able to identify the session in a view called v$session, and the columns EVENT and WAIT_CLASS should give an indication of what is happening on the Oracle end. 您的DBA应该能够在名为v $ session的视图中标识会话,并且EVENT和WAIT_CLASS列应该指示Oracle端发生的情况。

He would also be able to identify the SQL (SQL_ID from v$session) and use that in a SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(sql_id)) to determine the plan. 他还能够识别SQL(来自v $ session的SQL_ID)并在SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(sql_id))中使用它来确定计划。

If it is a development/test instance, see if he will grant you permissions to do that yourself if he (or she) is busy. 如果它是一个开发/测试实例,看看如果他(或她)忙,他是否会授予你自己这样做的权限。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM