2012-02-28 14 views
5

私たちのWebサーバ上のすべての履歴アクティビティを含むデータベースからApache要求を引き出し、必要に応じて迅速に回帰するためのインフラストラクチャを構築しようとしています。私たちは依然として小規模のクライアントからの要求を後退させることによってカバレッジを向上させるために、各クライアントに対する最大n個の要求(この質問のために、言い換えれば10個)を取得することによって要求を分散したいと考えています。グループごとの最大n個のSQLクエリへの高性能アプローチ

ここでは、最も近いものがSQL query to return top N rows per ID across a range of IDsであるように思われる多くの類似した質問が見つかりましたが、回答はほとんど私がすでに試したパフォーマンスに依存しない解決策でした。この表は、特定の日のために数百万のエントリが含まれており、このアプローチは、すべての行を読み込む必要ことを考えると、しかし

SELECT 
    * 
FROM 
    (
    SELECT 
     dailylogdata.*, 
     row_number() over (partition by dailylogdata.contextid order by occurrencedate) rn 
    FROM 
     dailylogdata 
    WHERE 
     shorturl in (?) 
    ) 
WHERE 
    rn <= 10; 

:たとえば、ROW_NUMBER()解析関数は、私たちが探している正確にデータを取得しますrow_number分析関数を適用するために私たちの選択基準に一致するインデックスがある場合、パフォーマンスはひどいです。私たちは、彼らのROW_NUMBERは、上記のクエリを実行してから10統計を超えたためにのみそれらの大半を捨てるために、ほぼ万行を選択して終了:

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 
|| Id | Operation       | Name     | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | Writes | OMem | 1Mem | Used-Mem | Used-Tmp|| 
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 
|| 0 | SELECT STATEMENT      |       |  1 |  | 12222 |00:09:08.94 |  895K| 584K| 301 |  |  |   |   || 
||* 1 | VIEW        |       |  1 | 4427K| 12222 |00:09:08.94 |  895K| 584K| 301 |  |  |   |   || 
||* 2 | WINDOW SORT PUSHED RANK   |       |  1 | 4427K| 13536 |00:09:08.94 |  895K| 584K| 301 | 2709K| 743K| 97M (1)| 4096 || 
|| 3 | PARTITION RANGE SINGLE   |       |  1 | 4427K| 932K|00:22:27.90 |  895K| 584K|  0 |  |  |   |   || 
|| 4 |  TABLE ACCESS BY LOCAL INDEX ROWID| DAILYLOGDATA   |  1 | 4427K| 932K|00:22:27.61 |  895K| 584K|  0 |  |  |   |   || 
||* 5 |  INDEX RANGE SCAN    | DAILYLOGDATA_URLCONTEXT |  1 | 17345 | 932K|00:00:00.75 | 1448 |  0 |  0 |  |  |   |   || 
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 
|                                             | 
|Predicate Information (identified by operation id):                                | 
|---------------------------------------------------                                | 
|                                             | 
| 1 - filter("RN"<=:SYS_B_2)                                     | 
| 2 - filter(ROW_NUMBER() OVER (PARTITION BY "DAILYLOGDATA"."CONTEXTID" ORDER BY "OCCURRENCEDATE")<=:SYS_B_2)                 | 
| 5 - access("SHORTURL"=:P1)                                     | 
|                                             | 
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 

しかし、代わりに我々は唯一の最初の10件の結果を照会場合このクエリを実行しているから

SELECT 
    * 
FROM 
    (
    SELECT 
     dailylogdata.* 
    FROM 
     dailylogdata 
    WHERE 
     shorturl in (?) 
     and contextid = ? 
    ) 
WHERE 
    rownum <= 10; 

統計:特定のContextIDのために、我々は劇的に速く、この実行することができます。この例では

|-------------------------------------------------------------------------------------------------------------------------| 
|| Id | Operation       | Name     | Starts | E-Rows | A-Rows | A-Time | Buffers || 
|-------------------------------------------------------------------------------------------------------------------------| 
|| 0 | SELECT STATEMENT     |       |  1 |  |  10 |00:00:00.01 |  14 || 
||* 1 | COUNT STOPKEY      |       |  1 |  |  10 |00:00:00.01 |  14 || 
|| 2 | PARTITION RANGE SINGLE   |       |  1 |  10 |  10 |00:00:00.01 |  14 || 
|| 3 | TABLE ACCESS BY LOCAL INDEX ROWID| DAILYLOGDATA   |  1 |  10 |  10 |00:00:00.01 |  14 || 
||* 4 |  INDEX RANGE SCAN    | DAILYLOGDATA_URLCONTEXT |  1 |  1 |  10 |00:00:00.01 |  5 || 
|-------------------------------------------------------------------------------------------------------------------------| 
|                               | 
|Predicate Information (identified by operation id):                  | 
|---------------------------------------------------                  | 
|                               | 
| 1 - filter(ROWNUM<=10)                        | 
| 4 - access("SHORTURL"=:P1 AND "CONTEXTID"=TO_NUMBER(:P2))                | 
|                               | 
+-------------------------------------------------------------------------------------------------------------------------+ 

を、Oracleがスマートです10の結果を得た後にデータを取り出すのを止めるのに十分です。 I は完全なcontextidsを収集し、各contextidに対してこのクエリの1つのインスタンスからなるクエリをプログラム的に生成することができますが、union allは完全に混乱していますが、コンテキストの数が限られているため、たとえそうでなくても、このアプローチはクルーグを引き起こす。

2番目のクエリに見合ったパフォーマンスを維持しながら、最初のクエリのシンプルさを維持するアプローチを知っている人はいますか?また、私は実際には安定した行のセットを検索することには気をつけません。私の基準を満たしていれば、回帰の目的のために問題ありません。

編集: Adam Muschの提案がトリックでした。私は彼の答えにコメントの応答に合わせることができないので、ここで彼の変更を加えてパフォーマンス結果を追加しています。また、私はここに、この時間をテストするために、より大きなデータセットを使用しています(キャッシュ)の統計では、比較のために私の元ROW_NUMBERアプローチから、次のとおりです。

|-------------------------------------------------------------------------------------------------------------------------------------------------| 
|| Id | Operation      | Name    | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem || 
|-------------------------------------------------------------------------------------------------------------------------------------------------| 
|| 0 | SELECT STATEMENT    |     |  1 |  | 12624 |00:00:22.34 | 1186K| 931K|  |  |   || 
||* 1 | VIEW       |     |  1 | 1163K| 12624 |00:00:22.34 | 1186K| 931K|  |  |   || 
||* 2 | WINDOW NOSORT    |     |  1 | 1163K| 1213K|00:00:21.82 | 1186K| 931K| 3036M| 17M|   || 
|| 3 | TABLE ACCESS BY INDEX ROWID| TWTEST   |  1 | 1163K| 1213K|00:00:20.41 | 1186K| 931K|  |  |   || 
||* 4 |  INDEX RANGE SCAN   | TWTEST_URLCONTEXT |  1 | 1163K| 1213K|00:00:00.81 | 8568 |  0 |  |  |   || 
|-------------------------------------------------------------------------------------------------------------------------------------------------| 
|                                     | 
|Predicate Information (identified by operation id):                        | 
|---------------------------------------------------                        | 
|                                     | 
| 1 - filter("RN"<=10)                               | 
| 2 - filter(ROW_NUMBER() OVER (PARTITION BY "CONTEXTID" ORDER BY NULL)<=10)                 | 
| 4 - access("SHORTURL"=:P1)                             | 
+-------------------------------------------------------------------------------------------------------------------------------------------------+ 

私はアダムの提案ダウンビットを沸騰の自由を撮影しました。ここで変更クエリ...

select 
    * 
from 
    twtest 
where 
    rowid in (
    select 
      rowid 
    from (
      select 
        rowid, 
        shorturl, 
        row_number() over (partition by shorturl, contextid 
                 order by null) rn 
      from 
        twtest 
    ) 
    where rn <= 10 
    and shorturl in (?) 
); 

だ...そしてその(キャッシュ)の評価から統計:

宣伝として
|--------------------------------------------------------------------------------------------------------------------------------------| 
|| Id | Operation     | Name    | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem || 
|--------------------------------------------------------------------------------------------------------------------------------------| 
|| 0 | SELECT STATEMENT   |     |  1 |  | 12624 |00:00:01.33 | 19391 |  |  |   || 
|| 1 | NESTED LOOPS    |     |  1 |  1 | 12624 |00:00:01.33 | 19391 |  |  |   || 
|| 2 | VIEW      | VW_NSO_1   |  1 | 1163K| 12624 |00:00:01.27 | 6770 |  |  |   || 
|| 3 | HASH UNIQUE    |     |  1 |  1 | 12624 |00:00:01.27 | 6770 | 1377K| 1377K| 5065K (0)|| 
||* 4 |  VIEW     |     |  1 | 1163K| 12624 |00:00:01.25 | 6770 |  |  |   || 
||* 5 |  WINDOW NOSORT   |     |  1 | 1163K| 1213K|00:00:01.09 | 6770 | 283M| 5598K|   || 
||* 6 |  INDEX RANGE SCAN  | TWTEST_URLCONTEXT |  1 | 1163K| 1213K|00:00:00.40 | 6770 |  |  |   || 
|| 7 | TABLE ACCESS BY USER ROWID| TWTEST   | 12624 |  1 | 12624 |00:00:00.04 | 12621 |  |  |   || 
|--------------------------------------------------------------------------------------------------------------------------------------| 
|                                  | 
|Predicate Information (identified by operation id):                     | 
|---------------------------------------------------                     | 
|                                  | 
| 4 - filter("RN"<=10)                            | 
| 5 - filter(ROW_NUMBER() OVER (PARTITION BY "SHORTURL","CONTEXTID" ORDER BY NULL NULL)<=10)          | 
| 6 - access("SHORTURL"=:P1)                           | 
|                                  | 
|Note                                 | 
|-----                                 | 
| - dynamic sampling used for this statement (level=2)                    | 
|                                  | 
+--------------------------------------------------------------------------------------------------------------------------------------+ 

、我々は唯一の完全濾過行に対してdailylogdataテーブルにアクセスしています。私はそれがが表示されていることを懸念していますが、まだ選択している(1213K)行の数に基づいてurlcontextインデックスのフルスキャンを実行していますが、コンテキスト固有の結果の数を増やすと、これは誤解を招く可能性があります)。

+0

同じ結果セット(グループごとに最大のN)を達成する他の方法やクエリを試したことがありますか? –

+0

2番目のクエリは「最初の10行」を取得せず、単純に10行を半ランダムに取得します。最初のクエリの1つのパーティションと真に等しくするために、内側のクエリに 'order by'を追加する必要があります。 – Allan

+0

ほとんどの場合、@ypercube同様のパフォーマンス結果を持つ他の分析関数を試しました。私は、必要なだけ多くの行を返さない[最初の](http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions056.htm#sthref1389)関数のようないくつかのより秘密のものを試しましたしかし、私はそれがこのクエリよりも優れていると思った。しかし、他の分析関数と同様に、必要以上にインデックスをスキャンしました。 – Trevor

答えて

4

これはjankyソリューションの一種であるが、それと思われます可能な限り速やかにインデックススキャンを実行し、フィルタリング条件と上位n個のクエリ基準の両方によって条件が満たされるまでデータを読み取らないようにします。

shorturl =の条件でテストされたことに注意してください。shorturl INの条件ではありません。

with rowid_list as 
(select rowid 
    from (select * 
      from (select rowid, 
         row_number() over (partition by shorturl, contextid 
              order by null) rn 
        from dailylogdata 
       ) 
      where rn <= 10 
     ) 
    where shorturl = ? 
) 
select * 
    from dailylogdata 
where rowid in (select rowid from rowid_list) 

with句はあなたの基準を満たしているshorturlcontextidの各ユニークな組み合わせのウィンドウNOSORTをフィルタリング最初の10行のROWIDをつかみます。次に、そのROWIDセットをループし、ROWIDごとにそれぞれをフェッチします。

---------------------------------------------------------------------------------------------------- 
| Id | Operation     | Name     | Rows | Bytes | Cost (%CPU)| Time  | 
---------------------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT   |      |  1 | 286 | 1536 (1)| 00:00:19 | 
| 1 | NESTED LOOPS    |      |  1 | 286 | 1536 (1)| 00:00:19 | 
| 2 | VIEW      | VW_NSO_1    | 136K| 1596K| 910 (1)| 00:00:11 | 
| 3 | HASH UNIQUE    |      |  1 | 3326K|   |   | 
|* 4 |  VIEW     |      | 136K| 3326K| 910 (1)| 00:00:11 | 
|* 5 |  WINDOW NOSORT   |      | 136K| 2794K| 910 (1)| 00:00:11 | 
|* 6 |  INDEX RANGE SCAN  | TABLE_REDACTED_INDEX | 136K| 2794K| 910 (1)| 00:00:11 | 
| 7 | TABLE ACCESS BY USER ROWID| TABLE_REDACTED  |  1 | 274 |  1 (0)| 00:00:01 | 
---------------------------------------------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 

    4 - filter("RN"<=10) 
    5 - filter(ROW_NUMBER() OVER (PARTITION BY "CLIENT_ID","SCE_ID" ORDER BY NULL NULL 
      )<=10) 
    6 - access("TABLE_REDACTED"."SHORTURL"=:b1) 
+0

私はこの解決法がどれほど良いかと信じています –

+0

素敵な解決策のように見え、それは私の最初の質問よりもかなり優れています。私はあなたの古いクエリと少し修正されたバージョンのパフォーマンス測定値を使って質問を更新しました。私は助けに感謝します! – Trevor

0

いつも取っているようです。 occurrenceDateはクラスタード・インデックスですか、そうでない場合は、クラスタード・インデックスで注文するように変更した方がはるかに迅速ですか?私。シーケンシャルなIDでクラスタ化されている場合は、その順番で並べ替えます。

+0

私は同意しません、インデックスの最初のキーは 'contextid'で区切られたフィールドでなければなりません。そして、' occurDate'の注文列 - これはデータがアクセスされる必要がある順序です。 –

+0

@JCooper私はいませんクエリを変更するだけで、ここでインデックスを変更することを提案します。また、ログデータの場合、速いINSERTのために索引が時系列である必要があるため、 'contextid'でクラスタリングしたくないでしょう。 –

+0

@TimRogersこの表にはクラスタード・インデックスはありません(以前はその用語になじみがありませんでしたが)。これを出現列の索引構成表に変換すると、同じ目標を達成できますか?私が使用している現在のインデックスは(shorturl、contextid)です。これは変更することができます。私の関心事は、row_number関数の使用により、データ構造にかかわらずcontextidごとに10を選択した後も、Oracleは呼び出しを知らないかもしれないということです。私はこれが役立つかどうか確認するためにいくつかのテストをします。 – Trevor

0

前回私は単純にを最後にキャッシュしました。最も興味深い行は小さいテーブルに入れました。私のデータ配布では、バルクテーブルを照会するのではなく、すべてのインサートでキャッシュテーブルを更新するほうが安かったです。

0

私はあなたが同じ結果セットを達成するために他の方法/クエリをチェックすべきだと思います。


Self-JOIN/GROUP BY

SELECT 
    d.* 
    , COUNT(*) AS rn 

FROM 
     dailylogdata AS d 
    LEFT OUTER JOIN 
     dailylogdata AS d2 
      ON d.contextid = d2.contextid 
      AND d.occurrencedate >= d2.occurrencedate) 
      AND d2.shorturl IN (?) 

WHERE 
    d.shorturl IN (?) 

GROUP BY 
    d.* 

HAVING 
    COUNT(*) <= 10 

そして、それが正常に動作する場合、私は見当がつかない別の1:

SELECT 
    d.* 
    , COUNT(*) AS rn 

FROM 
     (SELECT DISTINCT 
       contextid 
      FROM 
       dailylogdata 
      WHERE 
       shorturl IN (?) 
     ) AS dd 
    JOIN 
     dailylogdata AS d 
      ON d.PK IN 
       (SELECT 
         d10.PK 
        FROM 
         dailylogdata AS d10 
        WHERE 
         d10.contextid = dd.contextid 
        AND 
         d10.shorturl IN (?) 
        AND 
         rownum <= 10 
        ORDER BY 
         d10.occurrencedate 
       ) 
+0

自己結合は本当に悪い考えです。それはそれに続くすべての行に各行を結合します。同じ 'contextid'を持つ100行は、結果セットに4900行あります。 – Allan

+0

@Allan:私は100%同意します。自己結合は、テーブルに複数の異なるcontextIDがある場合(コンテキストIDごとに少数の行がある場合)に効果的です。 –

関連する問題