Oracle:条件付きの全文検索
-
28-10-2019 - |
質問
次のようなOracle Text Indexを作成しました。
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
s。
理想的には、アイテムのみを検索することをお勧めします 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
インデックスオン A
と B
私が言うことができる限り、インデックス検索を行うことができます 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)
.
解決
Oracle Text
1-コンテキストインデックスを作成することでパフォーマンスを向上させることができます ろ過します:
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の一貫した取得は18だけ必要です。しかし、マイナス面では、CTXCATインデックスの作成には11分近くかかり、1.8GBのスペースを使用しました。
(注:Oracle Textはここで正しく機能しているようですが、私はテキストに精通していませんし、これは@nulluserexceptionが言ったようにこれらのインデックスの不適切な使用ではありません。)
マルチカラムインデックスとインデックス結合
あなたがあなたの編集で説明する状況のために、 通常は (a、b)でインデックスを使用することと、AとBの個別のインデックスを結合することとの間には大きな違いはありません。説明したものと同様のデータを使用していくつかのテストを作成し、インデックス結合は7の一貫性のあるGETSと2つの一貫性のあるGETSのみを必要としますマルチカラムインデックス用。
この理由は、Oracleがブロックでデータを取得するためです。通常、ブロックは8Kで、インデックスブロックはすでにソートされているため、おそらく数ブロックで500〜2000の値を適合させることができます。パフォーマンスが心配な場合は、通常、ブロックを読み書きするIOが重要なことだけです。 Oracleが数千行を一緒に結合する必要があるかどうかは、取るに足らないCPU時間です。
ただし、これはOracle Text Indexesには当てはまりません。 Btreeインデックス(「bitmap and」)でコンテキストインデックスに参加できますが、パフォーマンスは低いです。
他のヒント
インデックスをかけます group_id
そして、それで十分かどうかを確認してください。あなたは、私たちが話している行の数や必要なパフォーマンスを言っているのではありません。
述語が処理される順序は、必ずしもあなたがそれらをクエリに書いた順序ではないことを忘れないでください。あなたが本当の理由がない限り、オプティマイザーをアウトマートしようとしないでください。
短縮版: それをする必要はありません。クエリオプティマイザーは、データを選択するための最良の方法を決定するのに十分賢いです。 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インスタンスを手元に持っていませんし、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行のみに述べています。