5

次のように当日のエントリを選択する Oracle SQL クエリを取得しました。

SELECT   [fields] 
FROM     MY_TABLE T 
WHERE    T.EVT_END BETWEEN TRUNC(SYSDATE) 
                       AND TRUNC(SYSDATE) + 86399/86400
  AND    T.TYPE = 123

一方、フィールドEVT_ENDのタイプは.DATET.TYPENUMBER(15,0)

テーブル データのサイズ (および進行中の時間) が大きくなると、日付の制約により、型の制約よりもはるかに大きな要素で結果セットが減少します。(種類が少ないため)

したがって、発生する基本的な問題は、現在の日付の選択をより速く行うために選択するのに最適なインデックスは何かということです。私は特にTRUNC(T.EVT_END)、通常のインデックスに対する関数インデックスの利点と欠点は何になるのだろうかT.EVT_ENDと考えています。関数インデックスを使用する場合、クエリは次のようになります。

SELECT   [fields] 
FROM     MY_TABLE T 
WHERE    TRUNC(T.EVT_END) = TRUNC(SYSDATE) 
  AND    T.TYPE = 123

他のクエリは、追加の型選択なしで (またはおそらく他のフィールドを使用して) 前述の日付制約を使用するため、複数列のインデックスはあまり役に立ちません。

ありがとう、私はあなたのヒントをいただければ幸いです。

4

5 に答える 5

4

インデックスは TYPE, EVT_END である必要があります。

CREATE INDEX PIndex
ON MY_TABLE (TYPE, EVT_END)

オプティマイザ プランは、最初にこのインデックスを調べて TYPE=123 セクションを見つけます。次に、TYPE=123 の下で EVT_END タイムスタンプがソートされるため、範囲内の最初の日付を b ツリーで検索し、データが範囲外になるまで日付を順番に調べます。

于 2012-11-21T13:54:05.007 に答える
3

上記のクエリに基づいて、機能インデックスは値を提供しません。関数インデックスを使用するには、クエリの述語を次のように記述する必要があります。

SELECT [fields] 
FROM MY_TABLE T 
WHERE TRUNC(T.EVT_END) BETWEEN TRUNC(SYSDATE) AND TRUNC(SYSDATE) + 86399/86400
  AND T.TYPE = 123

列 EVT_END の関数インデックスは無視されています。EVT_END の日付に通常のインデックスを作成することをお勧めします。関数インデックスを使用するには、条件の左側が関数インデックスの宣言と一致する必要があります。おそらく次のようにクエリを記述します。

SELECT [fields] 
FROM MY_TABLE T 
WHERE T.EVT_END BETWEEN TRUNC(SYSDATE) AND TRUNC(SYSDATE+1)
  AND T.TYPE = 123

そして、次のインデックスを作成します。

CREATE INDEX bla on MY_TABLE( EVT_END )

これは、1 日以内に終了したイベントを見つけようとしていると仮定しています。

于 2012-11-27T17:43:53.150 に答える
1

@ S1lence:
この質問の背後には、かなりの時間がかかると思います。そして、私は答えの推測を投稿するのが好きではないので、ここに私の答えを投稿するのに多くの時間を要しました。
FBIに対する日付列の通常のインデックスのこの選択に関する私のWeb検索の経験を共有したいと思います。以下のリンク
での私の理解に基づいて、TRUNC関数を確実に使用しようとしている場合は、このコンサルティングWebスペースに次のように記載されているように、通常のインデックスのオプションを打ち消すことができます。組み込み関数はインデックスを無効にし、不要なI/Oを伴う次善の実行を引き起こします。 私はそれがすべてをクリアすると思います。あなたが使うつもりならあなたはFBIと一緒に行かなければなりません

TRUNC確かに。私の返事が理にかなっているなら私に知らせてください。

関数ベースの索引を使用したOracleSQLTuning

乾杯、
ラクシュマナンC。

于 2012-11-28T11:00:41.740 に答える
1

関数ベースのインデックスを使用するかどうかの決定は、クエリの作成方法によって決定する必要があります。日付列に対するすべてのクエリがフォームTRUNC(EVT_END)である場合は、FBI を使用する必要があります。EVT_ENDただし、一般的には、次の理由だけでインデックスを作成することをお勧めします。

  • より再利用可能になります。1 日の特定の時間をチェックするクエリがある場合は、TRUNC を使用できません。
  • 日付だけを使用するインデックスには、より多くの個別のキーが存在します。1 日に 1,000 の異なる時間を挿入EVT_ENDすると、1,000 の個別のキーが作成されますが、キーTRUNC(EVT_END)は 1 つしかありません (これは、すべての日付の午前 0 時だけでなく、時間コンポーネントを保存していると仮定します。2 番目のケースでは、両方とも 1 になります)。 1 日の個別キー)。これは重要です。なぜなら、インデックスが持つ個別の値が多いほど、インデックスの選択性が高くなり、オプティマイザーによって使用される可能性が高くなるからです (これを参照) 。
  • クラスタリング係数は異なる可能性がありますが、 trunc を使用する場合、他のコメントで述べられているように、下降するのではなく上昇する可能性が高くなります。これは、クラスタリング係数が、インデックス内の値の順序がデータの物理ストレージとどの程度一致しているかを表すためです。すべてのデータが日付順に挿入されている場合、プレーン インデックスは物理データと同じ順序になります。ただし、TRUNC1 日のすべての時間は同じ値にマップされるため、インデックス内の行の順序は物理データとは完全に異なる場合があります。繰り返しますが、これは trunc インデックスが使用される可能性が低いことを意味します。ただし、これはデータベースの挿入/削除パターンに完全に依存します。
  • 開発者は、(私の経験では) 列に trunc が適用されていない場所に対してクエリを作成する可能性が高くなります。これが当てはまるかどうかは、開発者とデプロイされた SQL に関する品質管理に依存します。

TYPE, EVT_END個人的には、最初のパスとしてマーリンの答えに行きます。ただし、ご使用の環境でこれをテストし、TYPE 列と EVT_END 列を使用して、これがこのクエリと他のすべてのクエリにどのように影響するかを確認する必要があります。

于 2012-12-02T12:19:43.003 に答える
1

結果

インデックスがキャッシュされている場合、関数ベースのインデックスが最適に機能します。インデックスがキャッシュされていない場合は、圧縮された関数ベースのインデックスが最適に機能します。

以下は、私のテスト コードによって生成された相対時間です。低いほどよい。キャッシュありとキャッシュなしの数値を比較することはできません。これらはまったく異なるテストです。

                 In cache      Not in cache
Regular          120           139
FBI              100           138
Compressed FBI   126           100

FBI が通常のインデックスよりもパフォーマンスが優れている理由はわかりません。(おそらく、等式述語と範囲についてあなたが言ったことに関連しています。通常のインデックスには、説明計画に追加の「FILTER」ステップがあることがわかります。)圧縮されたFBIには、ブロックを解凍するための追加のオーバーヘッドがあります。この少量の余分な CPU 時間は、すべてが既にメモリ内にあり、CPU 待機が最も重要な場合に関係します。しかし、何もキャッシュされておらず、IO がより重要な場合、圧縮された FBI のスペースが削減されることは非常に役立ちます。

仮定

この質問には多くの混乱があるようです。私の読み方では、この 1 つの特定のクエリのみを気にし、関数ベースのインデックスと通常のインデックスのどちらが高速かを知りたいと考えています。

このインデックスの恩恵を受ける可能性のある他のクエリ、開発者がインデックスを使用することを覚えているかどうか、またはオプティマイザーがインデックスを選択するかどうかにかかわらず、インデックスを維持するために費やされる追加の時間は気にしないと思います。(オプティマイザがインデックスを選択しない場合は、可能性は低いと思いますが、ヒントを追加できます。) これらの仮定のいずれかが間違っている場合はお知らせください。

コード

--Create tables. 1 = regular, 2 = FBI, 3 = Compressed FBI
create table my_table1(evt_end date, type number) nologging;
create table my_table2(evt_end date, type number) nologging;
create table my_table3(evt_end date, type number) nologging;

--Create 1K days, each with 100K values
begin
    for i in 1 .. 1000 loop
        insert /*+ append */ into my_table1
        select sysdate + i - 500 + (level * interval '1' second), 1
        from dual connect by level <= 100000;

        commit;
    end loop;
end;
/
insert /*+ append */ into my_table2 select * from my_table1;
insert /*+ append */ into my_table3 select * from my_table1;

--Create indexes
create index my_table1_idx on my_table1(evt_end);
create index my_table2_idx on my_table2(trunc(evt_end));
create index my_table3_idx on my_table3(trunc(evt_end)) compress;

--Gather statistics
begin
    dbms_stats.gather_table_stats(user, 'MY_TABLE1');
    dbms_stats.gather_table_stats(user, 'MY_TABLE2');
    dbms_stats.gather_table_stats(user, 'MY_TABLE3');
end;
/

--Get the segment size.
--This shows the main advantage of a compressed FBI, the lower space.
select segment_name, bytes/1024/1024/1024 GB
from dba_segments
where segment_name like 'MY_TABLE__IDX'
order by segment_name;

SEGMENT_NAME     GB
MY_TABLE1_IDX    2.0595703125
MY_TABLE2_IDX    2.0478515625
MY_TABLE3_IDX    1.1923828125


--Test block.
--Uncomment different lines to generate 6 different test cases.
--Regular, Function-based, and Function-based compressed.  Both cached and not-cached.
declare
    v_count number;
    v_start_time number;
    v_total_time number := 0;
begin
    --Uncomment two lines to test the server when it's "cold", and nothing is cached.
    for i in 1 .. 10 loop
        execute immediate 'alter system flush buffer_cache';
    --Uncomment one line to test the server when it's "hot", and everything is cached.
    --for i in 1 .. 1000 loop

        v_start_time := dbms_utility.get_time;

        SELECT COUNT(*)
        INTO   V_COUNT
        --#1: Regular
        FROM   MY_TABLE1 T 
        WHERE  T.EVT_END BETWEEN TRUNC(SYSDATE) AND TRUNC(SYSDATE) + 86399/86400;
        --#2: Function-based
        --FROM   MY_TABLE2 T 
        --WHERE  TRUNC(T.EVT_END) = TRUNC(SYSDATE);
        --#3: Compressed function-based
        --FROM   MY_TABLE3 T 
        --WHERE  TRUNC(T.EVT_END) = TRUNC(SYSDATE);

        v_total_time := v_total_time + (dbms_utility.get_time - v_start_time);
    end loop;

    dbms_output.put_line('Seconds: '||v_total_time/100);
end;
/

試験方法

各ブロックを少なくとも 5 回実行し、実行の種類を交互に変えて (一部の時間だけマシンで何かが実行されていた場合に備えて)、高い実行時間と低い実行時間を捨てて、それらを平均しました。上記のコードには、この回答の 90% を占めるため、すべてのロジックが含まれているわけではありません。

その他の考慮事項

他にも考慮すべき点はまだたくさんあります。私のコードは、データが非常にインデックスに適した順序で挿入されることを前提としています。これが正しくない場合、圧縮がまったく役に立たない可能性があるため、状況はまったく異なります。

おそらく、この問題に対する最善の解決策は、パーティショニングで完全に回避することです。同じ量のデータを読み取る場合、フル テーブル スキャンはマルチブロック IO を使用するため、インデックス読み取りよりもはるかに高速です。ただし、オプションの購入に多額の費用が必要になることや、余分なメンテナンス作業が必要になることなど、パーティショニングにはいくつかの欠点があります。たとえば、事前にパーティションを作成したり、インターバル パーティショニングを使用したり (これには他にも奇妙な問題があります)、統計を収集したり、セグメントの作成を延期したりします。

最終的には、これを自分でテストする必要があります。ただし、このような単純な選択でさえテストするのは難しいことを忘れないでください。現実的なデータ、現実的なテスト、現実的な環境が必要です。現実的なデータは、思ったよりもはるかに難しいものです。索引では、単純にデータをコピーして索引を一度に作成することはできません。 create table my_table1 as select * fromテーブルを作成し、特定の順序で一連の挿入と削除を実行する場合とcreate index ...は異なるインデックスを作成します。

于 2012-12-02T23:18:05.520 に答える