11

次のようなOracleTextインデックスを作成しました。

create index my_idx on my_table (text) indextype is ctxsys.context; 

そして、私は次のことを行うことができます:

select * from my_table where contains(text, '%blah%') > 0;

しかし、このテーブルに別の列があるとしましょう。たとえばgroup_id、、代わりに次のクエリを実行したいとします。

select * from my_table where contains(text, '%blah%') > 0 and group_id = 43;

上記のインデックスを使用すると、Oracleは、を含むすべてのアイテムを検索してから'blah'、それらのすべてをチェックする必要がありgroup_idます。

理想的には、でアイテムのみを検索しgroup_id = 43たいので、次のようなインデックスが必要です。

create index my_idx on my_table (group_id, text) indextype is ctxsys.context; 

通常のインデックスのようなものなので、それぞれに対して個別のテキスト検索を実行できますgroup_id

Oracleでこのようなことをする方法はありますか(それが重要な場合は10gを使用しています)?

編集(説明)

100万行、次の2列、AおよびB、両方が数値であるテーブルについて考えてみます。500個の異なる値Aと2000個の異なる値がありB、各行が一意であるとします。

では、考えてみましょうselect ... where A = x and B = y

でインデックスを作成しAB私が知る限り、でインデックス検索を実行するBと、500の異なる行が返され、これらの行で結合/スキャンが実行されます。いずれの場合も、少なくとも500行を調べる必要があります(データベースが幸運で、必要な行を早期に見つけることは別として)。

上のインデックス(A,B)ははるかに効果的ですが、1つのインデックス検索で1つの行を検索します。

別々のインデックスを付けてgroup_id、私が感じるテキストは、クエリジェネレータに2つのオプションしか残していません。

(1)group_idインデックスを使用し、結果のすべての行をスキャンしてテキストを探します。
(2)テキストインデックスを使用し、結果のすべての行をスキャンして。を探しますgroup_id
(3)両方のインデックスを使用し、結合します。

私が欲しいのに対して:

(4)インデックスを使用して(group_id, "text")、特定のテキストインデックスを検索し、group_idそのテキストインデックスをスキャンして、必要な特定の行を探します。でインデックスを使用する場合のように、スキャンやチェック、結合は必要ありません(A,B)

4

4 に答える 4

8

Oracle Text

1- FILTER BYを使用してCONTEXTインデックスを作成することにより、パフォーマンスを向上させることができます。

create index my_idx on my_table(text) indextype is ctxsys.context filter by group_id;

私のテストではfilter by、パフォーマンスは確実に向上しましたが、group_idでbtreeインデックスを使用する方がわずかに高速でした。

2-CTXCATインデックスは「サブインデックス」を使用し、複数列のインデックスと同様に機能するようです。これはあなたが探しているオプション(4)のようです:

begin
  ctx_ddl.create_index_set('my_table_index_set');
  ctx_ddl.add_index('my_table_index_set', 'group_id');
end;
/

create index my_idx2 on my_table(text) indextype is ctxsys.ctxcat
    parameters('index set my_table_index_set');

select * from my_table where catsearch(text, 'blah', 'group_id = 43') > 0

これはおそらく最速のアプローチです。AおよびBシナリオと同様の120MBのランダムテキストに対して上記のクエリを使用すると、18回の一貫した取得のみが必要でした。ただし、欠点として、CTXCATインデックスの作成には約11分かかり、1.8GBのスペースを使用していました。

(注:Oracle Textはここでは正しく機能しているようですが、私はTextに精通しておらず、@ NullUserExceptionが言ったように、これがこれらのインデックスの不適切な使用ではないことを保証できません。)

複数列のインデックスとインデックス結合

編集で説明する状況では、通常、(A、B)でインデックスを使用することと、AとBで別々のインデックスを結合することの間に大きな違いはありません。私は、説明したものと同様のデータとインデックスを使用していくつかのテストを作成しました。結合に必要なのは7つの一貫した取得のみでしたが、複数列のインデックスでは2つの一貫した取得が必要でした。

これは、Oracleがデータをブロック単位で取得するためです。ブロックは通常8Kであり、インデックスブロックはすでに並べ替えられているため、500〜2000の値をいくつかのブロックに収めることができます。パフォーマンスが心配な場合は、通常、ブロックの読み取りと書き込みを行うIOだけが重要です。Oracleが数千行を結合する必要があるかどうかは、CPU時間の重要な量ではありません。

ただし、これはOracleText索引には適用されません。CONTEXTインデックスをbtreeインデックス(「ビットマップと」?)と結合することはできますが、パフォーマンスは低下します。

于 2011-09-16T04:35:18.917 に答える
1

インデックスを付けて、それでgroup_id十分かどうかを確認します。何行について話しているのか、どのようなパフォーマンスが必要なのかはわかりません。

述語が処理される順序は、必ずしもクエリで述語を記述した順序ではないことに注意してください。本当の理由がない限り、オプティマイザーの裏をかくことを試みないでください。

于 2011-09-10T14:53:07.067 に答える
1

短いバージョン:それをする必要はありません。クエリオプティマイザは、データを選択するための最良の方法を決定するのに十分賢いです。にbtreeインデックスを作成するだけgroup_idです。

CREATE INDEX my_group_idx ON my_table (group_id); 

長いバージョン:testperf.sql 136行のダミーデータを挿入するスクリプト()を作成しました。

DESC my_table;

Name     Null     Type      
-------- -------- --------- 
ID       NOT NULL NUMBER(4) 
GROUP_ID          NUMBER(4) 
TEXT              CLOB      

にbtreeインデックスがありますgroup_id。インデックスが実際に使用されるようにするには、これをdbaユーザーとして実行します。

EXEC DBMS_STATS.GATHER_TABLE_STATS('<YOUR USER HERE>', 'MY_TABLE', cascade=>TRUE);

それぞれの行group_id数と対応するパーセンテージは次のとおりです。

GROUP_ID               COUNT                  PCT                    
---------------------- ---------------------- ---------------------- 
1                      1                      1                      
2                      2                      1                      
3                      4                      3                      
4                      8                      6                      
5                      16                     12                     
6                      32                     24                     
7                      64                     47                     
8                      9                      7         

クエリオプティマイザがインデックスを使用するのは、それが適切であると判断した場合、つまり、特定の割合の行を取得している場合のみであることに注意してください。したがって、次のクエリプランを要求すると、次のようになります。

SELECT * FROM my_table WHERE group_id = 1;
SELECT * FROM my_table WHERE group_id = 7;

最初のクエリではインデックスを使用しますが、2番目のクエリでは行が多すぎてインデックスを有効にできないため、全表スキャンを実行しますgroup_id = 7

ここで、別の条件について考えてみましょう- WHERE group_id = Y AND text LIKE '%blah%'(私はあまり詳しくないのでctxsys.context)。

SELECT * FROM my_table WHERE group_id = 1 AND text LIKE '%ipsum%';

クエリプランを見ると、のインデックスgroup_id使用されていることがわかります。条件の順序は重要ではないことに注意してください。

SELECT * FROM my_table WHERE text LIKE '%ipsum%' AND group_id = 1;

同じクエリプランを生成します。また、で同じクエリを実行しようとするとgroup_id = 7、全表スキャンに戻ることがわかります。

SELECT * FROM my_table WHERE group_id = 7 AND text LIKE '%ipsum%';

統計は、クエリオプティマイザの有効性を継続的に向上させるために、Oracleによって毎日自動的に収集されることに注意してください(毎晩および週末に実行されるようにスケジュールされています)。つまり、Oracleはオプティマイザを最適化するために最善を尽くしているため、そうする必要はありません。

于 2011-09-11T16:49:59.070 に答える
0

テストするOracleインスタンスが手元になく、Oracleでフルテキストインデックスを使用していませんが、インラインビューで一般的に良好なパフォーマンスが得られました。これは、考えていた種類のインデックスの代替となる可能性があります。contains()が含まれている場合、次の構文は正当ですか?

このインラインビューは、グループ43の行のPK値を取得します。

             (
             select T.pkcol
             from T
             where group = 43
             )

グループに通常のインデックスがあり、カーディナリティが低い場合は、このセットをすばやく取得する必要があります。次に、そのセットを再びTと内部結合します。

           select * from T
           inner join
            (
             select T.pkcol
             from T
             where group = 43
             ) as MyGroup

           on T.pkcol = MyGroup.pkcol
           where contains(text, '%blah%') > 0

うまくいけば、オプティマイザーはPKインデックスを使用して結合を最適化し、次にグループ43行にのみcontains述部をappyできるようになります。

于 2011-09-10T12:41:20.553 に答える