11

それはとても簡単です。Oracle 11g に接続する SQL Developer でわずか数秒で実行されるクエリは、SSRS 2008 R2 では 15 ~ 25 分かかります。他のバージョンの SSRS は試していません。これまでのところ、VS 2008 からすべてのレポートの実行を行っています。

私は OLE DB プロバイダー "OraOLEDB.Oracle.1" を使用しています。これは、過去に Oracle プロバイダーを使用するよりも良い結果が得られたようです。

これが私がこれまでに決定できたものです:

• 遅延は DataSet の実行段階で発生し、結果セットやレンダリング時間とは関係ありません。(挿入先のテーブルから直接同じ行セットを選択することで証明します。)

• SSRS 自体はハングアップしていません。遅延が発生している場所であるOracleを本当に待っています(Oracle側からDBセッションを終了することで証明され、セッションが強制終了されたことについてSSRSでプロンプトエラーが発生しました)。

• :Parameter の形式のパラメーターを使用して直接クエリを実行してみました。より単純な私のクエリの非常に初期のバージョンは、直接クエリを実行しても問題なく動作しましたが、特定の複雑さを超えると、クエリは SSRS から永遠にかかり始めるように見えました。

• 次に、クエリ結果をテーブルまたはグローバル一時テーブルに挿入する SP の実行に切り替えました。これはしばらくの間、直接クエリよりも先に進むのに役立ちましたが、クエリの複雑さや長さの増加により、最終的にこの方法も壊れたようです。注: DataSource オプションで「単一のトランザクションを使用する」オプションをオンにすると、DataSet が rdl ファイルに表示される順序で実行されるため、テーブルに入力する SP を実行すると機能します。フィールドを返さない DataSet は、すべてのパラメーターが満たされている限り実行されます。

• テーブルを返す関数を試してみましたが、SQL Developer でリテラル パラメータを使用した直接呼び出しを行うと 1 ~ 5 秒で返されますが、改善は見られませんでした。

• 問題のデータベースには統計がありません。これはベンダーによって作成された製品の一部であり、統計を作成/更新するための時間や経営陣の同意がありませんでした. 動的に統計を計算するために DYNAMIC_SAMPLING ヒントを試してみたところ、より良い実行計画が得られました。したがって、結合順序を強制し、戦略的なハッシュ結合を使用するようにクエリ ヒントを挿入して、実行時間をわずか数秒に短縮しました。戻って、これらの実行ヒントを使用して SSRS で直接クエリを実行することはしませんでした。

• トレース (または Oracle に相当するもの) を設定した Oracle DBA の助けを借りました。残念ながら、彼の時間は限られているため、サーバー側で何が実行されているかを突き止めることができませんでした。私にはこれを素早く行う経験がありませんし、自分でこれを行う方法を勉強する時間もありません。何が起こっているのかを判断するために何をすべきかについての提案をいただければ幸いです。

私の唯一の仮説は次のとおりです。

• クエリが何らかの理由で不適切な実行計画を取得している。たとえば、数百ではなく数万の「左」または外側のループ行がある場合に、HASH 結合の代わりに LOOP 結合を不適切に使用する。

• SSRS は、妥当なものではなく nvarchar(4000) などのパラメーターを送信する可能性があります。また、Oracle SP および関数パラメーターには長さの指定がなく、クエリ呼び出しから実行長を取得するため、パラメーター スニッフィングなどのプロセスが実行されます。前のポイントのように実行計画を台無しにします。

• クエリが何らかの形で SSRS/プロバイダーによって書き換えられている。多値パラメーターを使用していますが、そのままではありません。パラメーターは式 Join(Parameters!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 の知識を翻訳するために) すべて学習した後でも、最初に実際のテーブルの結果GLOBAL TEMPORARY TABLEを次に、そこからデータを抽出します。DYNAMIC_SAMPLING良い実行計画が得られたので、結合ヒントとアクセスヒントを使用してコピーしました。最終的な SP は次のとおりです (Oracle では、その関数が SELECT ステートメント内で呼び出されたときに関数で DML を実行できないため、関数にすることはできません)。

swap_join_inputsandなどの結合ヒントを無視していたことを断言することもありno_swap_join_inputsますが、私の読書から、オラクルは実際に使用できない場合、または何か間違ったことをしている場合にのみヒントを無視するようです。幸いなことに、テーブルは適切にスワップされました ( 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;

これは長くてつらい道のりでしたが、私が学んだことが1つあるとすれば、適切に更新された統計なしでデータベースで作業しているということです(ベンダーは気にしませんが、DBAに追加してもらうことを検討します)それらについて)は、合理的な時間内に物事を終わらせたいと思っている人にとっては、本当の惨事になる可能性があります。

4

2 に答える 2

1

クエリを投稿すると役立つ場合があります。

DBA は v$session と呼ばれるビューでセッションを識別できる必要があり、列 EVENT および WAIT_CLASS は Oracle 側で何が起こっているかを示す必要があります。

また、SQL (v$session からの SQL_ID) を識別し、それを SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(sql_id)) で使用して計画を決定することもできます。

開発/テスト インスタンスの場合は、彼 (または彼女) が忙しい場合に、自分でそれを行う権限を付与してくれるかどうかを確認してください。

于 2011-01-25T02:00:04.687 に答える
1

これが古いことは知っていますが、同様の問題があり、nsl_sort を binary_ci ではなく binary に設定する必要がありました。セッションをバイナリに設定してみることができます: alter session set nls_sort=binary

于 2013-08-01T21:46:20.897 に答える