結果
インデックスがキャッシュされている場合、関数ベースのインデックスが最適に機能します。インデックスがキャッシュされていない場合は、圧縮された関数ベースのインデックスが最適に機能します。
以下は、私のテスト コードによって生成された相対時間です。低いほどよい。キャッシュありとキャッシュなしの数値を比較することはできません。これらはまったく異なるテストです。
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 ...
は異なるインデックスを作成します。