アクセス・パスは、問合せで行ソースから行を取り出すために使用される手法です。
この章の内容は次のとおりです。
行ソースは、実行計画のステップによって戻される行のセットです。表、ビュー、または結合処理あるいはグループ化処理の結果が行ソースになる可能性があります。
アクセス・パスなどの単項演算は、行ソースから行を取得するために問合せで使用される手法であり、単一の行ソースを入力として受け入れます。たとえば、全表スキャンは、単一行ソースの行の取得です。対照的に、結合操作はバイナリで、2つの行ソースから入力を受け取ります。
データベースでは、リレーショナル・データ構造ごとに異なるアクセス・パスを使用します。次の表は、主なデータ構造に対する一般的なアクセス・パスについてまとめたものです。
表8-1 データ構造とアクセス・パス
アクセス・パス | ヒープ構成表 | Bツリー索引およびIOT | ビットマップ索引 | 表クラスタ |
---|---|---|---|---|
× |
||||
× |
||||
× |
||||
× |
||||
× |
||||
× |
||||
× |
||||
× |
||||
× |
||||
× |
||||
× |
||||
× |
||||
ビットマップ索引レンジ・スキャン |
× |
|||
× |
||||
× |
オプティマイザは、使用可能な様々な実行計画を検討し、各計画にコストを割り当てます。オプティマイザは、コストが最も低い計画を選択します。一般に、表の行の小さいサブセットを取得する文には索引アクセス・パスのほうが効率的ですが、表の大きな部分にアクセスする場合は全表スキャンのほうが効率的です。
関連項目:
これらの構造の概要は、『Oracle Database概要』を参照してください。
表は、Oracle Databaseにおけるデータ編成の基本単位です。
リレーショナル表は、最も一般的な表タイプです。リレーショナル表には次のような構成上の特徴があります。
ヒープ構成表では、行は特定の順序で格納されません。
索引構成表では、主キー値に従って行が整列されます。
外部表は読取り専用の表であり、これらのメタデータはデータベースに格納されますが、データはデータベースの外部に格納されます。
この項には、ヒープ構成表へのオプティマイザのアクセス・パスについて説明する次のトピックがあります。
関連項目:
表の概要は、『Oracle Database概要』を参照してください。
表の管理方法の詳細は、『Oracle Database管理者ガイド』を参照してください。
デフォルトでは、表はヒープとして編成されます。つまり、データベースでは、ユーザーが指定する順序ではなく、最も収まりすいように行が配置されます。
ユーザーが行を追加すると、データベースではデータ・セグメントの最初の使用可能な空き領域に行が格納されます。挿入された順序どおりに行が取得されることは保証されていません。
データベースでは、行がデータ・ブロックに格納されます。表では、ブロックの下部の任意の行へ書込みが行われます。Oracle Databaseは、ブロック自体を管理するために、行ディレクトリと表ディレクトリが含まれているブロック・オーバーヘッドを使用します。
エクステントは論理的に連続するデータ・ブロックから構成されます。ブロックはディスク上で物理的に連続していない可能性もあります。セグメントは、表領域内の特定の論理記憶域構造のデータがすべて入っている、エクステントの集合です。たとえば、Oracle Databaseでは、1つ以上のエクステントを割り当てることによって、表のデータ・セグメントを形成します。また、データベースは、表の索引セグメントを作成するためにも、1つ以上のエクステントを割り当てます。
デフォルトでは、データベースは永続的なローカル管理表領域に対して、自動セグメント領域管理(ASSM)を使用します。セッションが最初にデータを表に挿入すると、データベースはビットマップ・ブロックをフォーマットします。ビットマップはセグメントのブロックを追跡します。データベースでは、ビットマップを使用して空きブロックが検索され、各ブロックは書き込み前にフォーマットされます。ASSMを使用すると、挿入処理が各ブロックに分散されるため、同時実行性の問題を回避できます。
最高水位標(HWM)は、セグメント内の位置を指し示し、その位置を超えると、データ・ブロックは未フォーマットであり、使用されていません。HWMより下のブロックは、フォーマット済で書き込まれている、フォーマット済で空である、あるいは未フォーマットである場合があります。低い最高水位標(低いHWM)は、この位置より下のすべてのブロックは、データが格納されているか、以前にデータが格納されていたため、フォーマット済であることを示しています。
全表スキャン時は、データベースはフォーマット済であることが既知である低いHWMまでのブロックをすべて読み取り、次に、セグメント・ビットマップを読み取って、HWMと低いHWMの間にあるどのブロックがフォーマット済で安全に読み取れるのかを判別します。データベースは、ブロックが未フォーマットであるため、HWMを超えて読み取ることはありません。
関連項目:
データ・ブロックの記憶域の詳細は、『Oracle Database概要』を参照してください。
ヒープ構成表のすべての行にはその表で一意のROWIDがあり、これは行断片の物理アドレスに相当します。ROWIDは行の10バイトの物理アドレスです。
ROWIDは特定のファイル、ブロックおよび行番号を指します。たとえば、ROWID AAAPecAAFAAAABSAAA
では、最後のAAA
は行番号を表します。行番号は行ディレクトリ・エントリの索引です。行ディレクトリ・エントリにはブロックにおける行の位置へのポインタが含まれます。
データベースはブロックの下部で行を移動することがあります。たとえば、行の移動が可能な場合、行は、パーティション・キーの更新、フラッシュバック表の操作、表の縮小操作などのために移動できます。データベースがブロック内で行を移動する場合、データベースは行ディレクトリ・エントリを更新してポインタを修正します。ROWIDは変わりません。
Oracle Databaseでは、ROWIDを内部的に使用して索引が構築されます。たとえば、Bツリー索引のそれぞれのキーは、対応する行のアドレスを指すROWIDに関連付けられています。物理ROWIDを使用すると、表の1つの行に最速でアクセスでき、データベースではわずか1回のI/Oで行を取得できます。
関連項目:
ROWIDの詳細は、『Oracle Database概要』を参照してください。
ダイレクト・パス読取りでは、データベースは、SGA全体を回避しながら、ディスクからPGAに直接バッファを読み込みます。
次の図は、SGAにバッファを格納する散布読取りおよび順次読取りと、ダイレクト・パス読取りの違いを示しています。
Oracle Databaseがダイレクト・パス読取りを実行する可能性のある状況には、次のようなものがあります。
CREATE TABLE AS SELECT
文の実行
ALTER REBUILD
文またはALTER MOVE
文の実行
一時表領域からの読取り
パラレル問合せ
LOBセグメントからの読取り
関連項目:
ダイレクト・パス読取りの待機イベントの詳細は、『Oracle Databaseパフォーマンス・チューニング・ガイド』を参照してください。
全表スキャンでは、表のすべての行が読み取られ、選択基準を満たしていない行がフィルタ処理されます。
一般に、オプティマイザは、別のアクセス・パスを使用できない場合、または使用可能な別のアクセス・パスのコストが高い場合は、全表スキャンを選択します。
次の表では、全表スキャンを選択する一般的な理由を示します。
表8-2 全表スキャンの一般的な理由
理由 | 説明 | 詳細情報 |
---|---|---|
索引が存在しない。 |
索引が存在しない場合、オプティマイザは、全表スキャンを使用します。 |
『Oracle Database概要』 |
問合せ述語が、索引付けされた列に関数を適用する。 |
索引がファンクション索引でない場合、関数が適用された列の値ではなく、その列の値が索引付けされます。典型的なアプリケーション・レベルの誤りは、 |
「パフォーマンスを考慮したファンクション索引の使用方法に関するガイドライン」 |
|
索引にはNULLエントリを含めることはできないため、オプティマイザは、索引を使用して、表の行数をカウントすることができません。 |
「Bツリー索引およびNULL」 |
問合せ述語では、最先端のBツリー索引を使用することができない。 |
たとえば、 |
「索引スキップ・スキャン」 |
問合せは非選択性である。 |
表のほとんどのブロックを必要とする問合せの場合、オプティマイザは、索引が使用可能であっても、全表スキャンを使用します。全表スキャンではより大きなI/Oコールを使用できます。大きなI/Oコールを少なくすることにより、小さなコールを何度も行うよりもコストが低くなります。 |
|
表統計が失効している。 |
たとえば、以前は小さかった表が大きくなったとします。表統計が失効しており、表の現在のサイズを反映していない場合、オプティマイザは、現在では全表スキャンではなく索引が最も効率的であることを認識できません。 |
「オプティマイザ統計の概要」 |
表が小さい。 |
表に含まれているブロックがn個よりも少なく、最高水位標を下回っている場合(ここで、nは、 |
『Oracle Databaseリファレンス』 |
表の並列度が高い。 |
表の並列度が高いと、レンジ・スキャンよりも全表スキャンの方向にオプティマイザを偏らせます。並列度を判断するには、 |
『Oracle Databaseリファレンス』 |
問合せで全表スキャンのヒントが使用される。 |
ヒント |
『Oracle Database SQL言語リファレンス』 |
全表スキャンでは、データベースは、最高水位標を下回るすべてのフォーマット済ブロックを順に読み取ります。データベースは、各ブロックを1回のみ読み取ります。
次の図は、表セグメントのスキャンで、最高水位標を下回る未フォーマットのブロックがどのようにスキップされるかを示しています。
ブロックは隣接しているため、データベースでは、I/Oコールを単一ブロックより大きくすることでスキャンを高速化できます。これは、マルチブロック読取りと呼ばれています。読取りコールのサイズは、1ブロックからDB_FILE_MULTIBLOCK_READ_COUNT
初期化パラメータで指定されているブロック数までの範囲になります。たとえば、このパラメータを4
に設定すると、データベースは単一コールで最大4ブロックを読み取ります。
全表スキャン中の、ブロックのキャッシングのアルゴリズムは複雑です。たとえば、表が小さいか大きいかによって、ブロックがキャッシュされる方法は異なります。
関連項目:
デフォルトのキャッシュ・モードの概要は、『Oracle Database概要』を参照してください。
DB_FILE_MULTIBLOCK_READ_COUNT
初期化パラメータの詳細は、『Oracle Databaseリファレンス』を参照してください。
次の文は、hr.employees
表で4000を超える給与を問い合せます。
SELECT salary FROM hr.employees WHERE salary > 4000;
例8-1 全表スキャン
次の計画は、DBMS_XPLAN.DISPLAY_CURSOR
関数を使用して取得されました。salary
列には索引が存在しないため、オプティマイザは、索引レンジ・スキャンを使用できず、全表スキャンを使用します。
SQL_ID 54c20f3udfnws, child number 0
-------------------------------------
select salary from hr.employees where salary > 4000
Plan hash value: 3476115102
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 3 (100)| |
|* 1 | TABLE ACCESS FULL| EMPLOYEES | 98 | 6762 | 3 (0)| 00:00:01 |
-------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("SALARY">4000)
ROWIDは、データの格納場所の内部表現です。
行のROWIDは、行が含まれるデータファイルとデータ・ブロック、およびブロック内での行の位置を指定します。ROWIDの指定による行の位置特定は、単一行を取得する最も高速な方法です。これは、取得する行のデータベース内での正確な位置が指定されるためです。
注意:
ROWIDは、バージョン間で変わってしまう場合があります。行は移動する可能性があるため、位置に基づくアクセスはお薦めしません。
関連項目:
ROWIDの詳細は、『Oracle Database開発ガイド』を参照してください。
ほとんどの場合、データベースは1つ以上の索引のスキャン後にROWIDによって表にアクセスします。しかし、ROWIDによる表アクセスでは、すべての索引スキャンに従う必要はありません。必要な列がすべて索引に含まれていると、ROWIDによるアクセスは行われない場合があります(「高速全索引スキャン」を参照)。
ROWIDによって表にアクセスするには、データベースで次の手順を実行します。
文のWHERE
句または1つ以上の索引の索引スキャンのいずれかを使用して、選択した行のROWIDを取得します。
索引内に存在しない文の中の列には、表アクセスが必要になる場合があります。
ROWIDに基づいて、表内で選択された各行の位置を特定します。
次の問合せを実行するとします。
SELECT * FROM employees WHERE employee_id > 190;
次の計画の手順2は、hr.employees
表にあるemp_emp_id_pk
索引のレンジ・スキャンを示しています。データベースは索引から取得したROWIDを使用して、employees
表から対応する行を検索し、取得します。手順1に示されているBATCHED
アクセスは、データベースが索引からいくつかのROWIDを取得してからブロック順に行へのアクセスを試行することで、クラスタリングが改善され、データベースがブロックにアクセスしなければならない回数が減ることを意味します。
-------------------------------------------------------------------------------- |Id| Operation | Name |Rows|Bytes|Cost(%CPU)|Time| -------------------------------------------------------------------------------- | 0| SELECT STATEMENT | | | |2(100)| | | 1| TABLE ACCESS BY INDEX ROWID BATCHED|EMPLOYEES |16|1104|2 (0)|00:00:01| |*2| INDEX RANGE SCAN |EMP_EMP_ID_PK|16| |1 (0)|00:00:01| -------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("EMPLOYEE_ID">190)
サンプル表スキャンでは、単純な表、または結合およびビューを含む文などの複合SELECT
文からデータのランダムなサンプルが取り出されます。
文のFROM
句にSAMPLE
キーワードが含まれている場合、データベースでは、サンプル表スキャンが使用されます。
SAMPLE
句には次の形式があります。
SAMPLE
(
sample_percent
)
データベースは、表内の行を指定の割合だけ読み取って、サンプル表スキャンを実行します。
SAMPLE BLOCK
(
sample_percent
)
データベースは、表ブロックを指定の割合だけ読み取って、サンプル表スキャンを実行します。
sample_percentには、行またはブロックの合計数の何パーセントがサンプルに含まれるかを指定します。.000001
以上100
未満の範囲の値を指定します。この割合は、各行(ブロック・サンプリングでは行の各クラスタ)が、サンプル用に選択される可能性を示します。データベースがsample_percent分の行を厳密に取り出すということではありません。
注意:
ブロック・サンプリングは、全表スキャン中または高速全索引スキャン中にのみ使用可能です。より効率的な実行パスが存在する場合、ブロック・サンプリングは実行されません。特定の表または索引に対するブロック・サンプリングを確実に実行する場合は、FULL
またはINDEX_FFS
のヒントを使用します。
関連項目:
SAMPLE
句の詳細は、『Oracle Database SQL言語リファレンス』を参照してください。
例8-2は、サンプル表スキャンを使用して、行ではなくブロックによるサンプリングを行ってemployees
表の1%にアクセスします。
例8-2 サンプル表スキャン
SELECT * FROM hr.employees SAMPLE BLOCK (1);
この文のEXPLAIN
PLAN
出力は、次のような形式になります。
------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| ------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 68 | 3 (34)| | 1 | TABLE ACCESS SAMPLE | EMPLOYEES | 1 | 68 | 3 (34)| -------------------------------------------------------------------------
Oracle Database 12c リリース1 (12.1.0.2)以降、インメモリー・スキャンはインメモリー列ストア(IM列ストア)から一部またはすべての行を取得します。IM列ストアは任意に入力できるSGA領域です。ここには、迅速にスキャンできるように最適化された特殊な列形式で、表のコピーおよびパーティションが格納されています。
関連項目:
IM列ストアの概要は、Oracle Database概要を参照
IM列ストアの有効化の詳細は、『Oracle Database管理者ガイド』を参照
次のデータベース初期化パラメータは、インメモリー機能に影響します。
INMEMORY_QUERY
このパラメータでは、セッションまたはシステム・レベルでデータベースのインメモリー問合せを有効または無効にします。このパラメータは、IM列ストアを使用する場合と使用しない場合でワークロードをテストする際に役立ちます。
OPTIMIZER_INMEMORY_AWARE
このパラメータでは、オプティマイザ・コスト・モデル、表拡張、ブルーム・フィルタなどに対して行われたすべてのインメモリー拡張を有効(TRUE
)または無効(FALSE
)にします。パラメータをFALSE
に設定すると、オプティマイザはSQL文の最適化中に表のインメモリー・プロパティを無視します。
OPTIMIZER_FEATURES_ENABLE
12.1.0.2
より小さい値に設定した場合、このパラメータの効果はOPTIMIZER_INMEMORY_AWARE
をFALSE
に設定した場合と同じです。
インメモリー問合せを有効または無効にするには、INMEMORY
またはNO_INMEMORY
ヒントを指定できます。これは、問合せごとのINMEMORY_QUERY
初期化パラメータと同じです。SQL文でINMEMORY
ヒントを使用し、それが参照するオブジェクトがIM列ストアにまだロードされていない場合、データベースは文を実行する前にIM列ストアにオブジェクトが移入されるのを待機しません。ただし、オブジェクトの初期アクセスにより、IM列ストア内のオブジェクトの移入がトリガーされます。
関連項目:
INMEMORY_QUERY、OPTIMIZER_INMEMORY_AWAREおよびOPTIMIZER_FEATURES_ENABLE
初期化パラメータの詳細は、『Oracle Databaseリファレンス』
を参照してください。
INMEMORYヒントの詳細は、『Oracle Database SQL言語リファレンス』を参照してください。
次の例に、INMEMORY HIGH
オプションで変更されたoe.product_information
表の問合せを示します。
例8-3 インメモリー表スキャン
SELECT * FROM oe.product_information WHERE list_price > 10 ORDER BY product_id
この文の計画は次のようになり、ステップ2のINMEMORY
キーワードはオブジェクトの一部またはすべてがIM列ストアからアクセスされたことを示します。
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR);
SQL_ID 2mb4h57x8pabw, child number 0
-------------------------------------
select * from oe.product_information where list_price > 10 order byproduct_id
Plan hash value: 2256295385
--------------------------------------------------------------------------------------------
|Id| Operation | Name |Rows|Bytes |TempSpc|Cost(%CPU)|Time|
--------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT | | | | |21 (100)| |
| 1| SORT ORDER BY | | 285| 62415|82000|21 (5)|00:00:01|
|*2| TABLE ACCESS INMEMORY FULL| PRODUCT_INFORMATION | 285| 62415| | 5 (0)|00:00:01|
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - inmemory("LIST_PRICE">10)
filter("LIST_PRICE">10)
索引は、表または表クラスタに関連するオプションの構造であり、索引によってデータ・アクセスを高速化できる場合があります。表の1つ以上の列に索引を作成することによって、場合によって、ランダムに分散している行の小さなセットを表から取得できるようになります。索引は、ディスクI/Oを削減するための様々な手段のうちの1つです。
関連項目:
索引の概要については、『Oracle Database概要』を参照してください。
索引の管理方法の詳細は、『Oracle Database管理者ガイド』を参照してください。
Bツリー(バランス・ツリーの略)は、最も一般的なタイプのデータベース索引です。
Bツリー索引は、複数の範囲に分割された順序付きの値リストです。Bツリーは、キーを行または行の範囲と関連付けることによって、完全一致や範囲検索など、広範囲の問合せに対して優れた検索パフォーマンスを提供します。
この項の内容は次のとおりです。
Bツリー索引には、検索用のブランチ・ブロックと、値を格納するリーフ・ブロックの2種類のブロックがあります。
次の図に、Bツリー索引の論理構造を示します。ブランチ・ブロックには、2つのキーの分岐を決定する際に必要な、最小のキー接頭辞が格納されます。リーフ・ブロックには、すべての索引付きデータ値と、実際の行を検索するための対応するROWIDが含まれています。各索引エントリは(キー、ROWID)によってソートされます。リーフ・ブロックは二重にリンクされます。
ビットマップ索引ブロックは、索引セグメントのどこにでも出現することができます。
図8-3は、互いに隣接するリーフ・ブロックを示しています。たとえば、1-10
のブロックは、11-19
のブロックの前であり、隣り合っています。この順序は、索引エントリを接続するリンクされたリストを示しています。ただし、索引ブロックは、索引セグメント内で順番に格納される必要はありません。たとえば、246から250
ブロックは、1から10
ブロックの直前を含め、セグメント内のどこにでも出現することができます。このため、順序付き索引スキャンは単一ブロックI/Oを実行する必要があります。データベースは、索引ブロックを読み取ることでどの索引ブロックを次に読み取るかを判断する必要があります。
索引ブロック本体は、表の行と同様に、索引エントリをヒープに格納します。たとえば、値10
が最初に表に挿入された場合、キーが10
の索引エントリは索引ブロックの最下部に挿入される可能性があります。0
が次に表に挿入された場合、キー0
の索引エントリは10
のエントリの上に挿入される可能性があります。したがって、ブロック本体の索引エントリは、キーの順には格納されません。ただし、索引ブロック内では、行ヘッダーはレコードをキーの順に格納します。たとえば、ヘッダーの最初のレコードはキー0
を含む索引エントリを指し、以下同様に、キー10
の索引エントリを指すレコードまで順に続きます。したがって、索引スキャンは行ヘッダーを読み取ることで、レンジ・スキャンをどこで開始し終了するかを判断できます。それにより、ブロックのすべてのエントリを読み取る必要性が回避されます。
関連項目:
索引ブロックの詳細は、『Oracle Database概要』を参照してください。
非一意索引では、データベースはROWIDを格納するために、追加列としてROWIDをキーに付加したものを格納します。このエントリによって、キーが一意になる長さのバイト数が追加されます。
たとえば、図8-3に示された非一意の索引の最初の索引キーは、ペアの0,rowid
であり、単なる0
ではありません。データは索引キーの値でソートされてから、ROWIDの昇順でソートされます。たとえば、エントリは次のように並べ替えられます。
0,AAAPvCAAFAAAAFaAAa 0,AAAPvCAAFAAAAFaAAg 0,AAAPvCAAFAAAAFaAAl 2,AAAPvCAAFAAAAFaAAm
一意の索引では、索引キーにROWIDは含まれません。データは、0
、1
、2
といった索引キーの値のみでソートされます。
関連項目:
一意および非一意の索引の概要は、『Oracle Database概要』を参照してください。
Bツリー索引では、NULLキーはオプティマイザのアクセス・パスの選択方法として重要であるため、まったく格納されないというわけではありません。このルールの結果として、単一列Bツリー索引ではNULLは格納されません。
例を使って説明します。hr.employees
表にはemployee_id
に主キー索引があり、department_id
に一意索引があります。department_id
列はNULLを含むことができるNULL値可能列ですが、employee_id
列はNULLを含むことはできません。
SQL> SELECT COUNT(*) FROM employees WHERE department_id IS NULL; COUNT(*) ---------- 1 SQL> SELECT COUNT(*) FROM employees WHERE employee_id IS NULL; COUNT(*) ---------- 0
次の例は、hr.employees
のすべての部門IDの問合せのためにオプティマイザが全表スキャンを選択することを示しています。オプティマイザは、表のすべての行にエントリがあるとは保証されていないため、employees.department_id
で索引を使用することができません。
SQL> EXPLAIN PLAN FOR SELECT department_id FROM employees; Explained. SQL> SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY()); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Plan hash value: 3476115102 ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 107 | 321 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS FULL| EMPLOYEES | 107 | 321 | 2 (0)| 00:00:01 | ------------------------------------------------------------------------------- 8 rows selected.
次の例は、NULL以外の列はすべて索引付けされるため、オプティマイザは特定の部門IDの問合せのためにdepartment_id
の索引を使用できることを示しています。
SQL> EXPLAIN PLAN FOR SELECT department_id FROM employees WHERE department_id=10; Explained. SQL> SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY()); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Plan hash value: 67425611 -------------------------------------------------------------------------------- | Id| Operation | Name | Rows |Bytes| Cost (%CPU)| Time | -------------------------------------------------------------------------------- PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 3 | 1 (0)| 00:0 0:01| |*1 | INDEX RANGE SCAN| EMP_DEPARTMENT_IX | 1 | 3 | 1 (0)| 00:0 0:01| -------------------------------------------------------------------------------- Predicate Information (identified by operation id): PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- 1 - access("DEPARTMENT_ID"=10)
次の例は、述語によってNULL値が除外される場合にオプティマイザは索引スキャンを選択することを示しています。
SQL> EXPLAIN PLAN FOR SELECT department_id FROM employees WHERE department_id IS NOT NULL; Explained. SQL> SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY()); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Plan hash value: 1590637672 -------------------------------------------------------------------------------- | Id| Operation | Name | Rows|Bytes| Cost (%CPU)| Time | -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 106 | 318 | 1 (0)| 00:0 0:01 | |*1 | INDEX FULL SCAN | EMP_DEPARTMENT_IX | 106 | 318 | 1 (0)| 00:0 0:01 | -------------------------------------------------------------------------------- Predicate Information (identified by operation id): PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- 1 - filter("DEPARTMENT_ID" IS NOT NULL)
索引一意スキャンはROWID 1までを返します。
索引一意スキャンには、等価述語が必要です。
具体的には、次の条件が当てはまる場合にのみ、データベースは一意スキャンを実行します。
問合せ述語が、等価演算子を使用して一意索引キーのすべての列を参照する(WHERE prod_id=10
など)。
CREATE UNIQUE INDEX
文で作成された索引によって参照される列に等価述語を含むSQL文がある。
一意キー制約または主キー制約のみでは、索引一意スキャンを作成するのに不十分です。非一意索引付きの列に主キー制約を作成し、結果として索引一意スキャンではなく索引レンジ・スキャンとなる例を考えてみます。
CREATE TABLE t_table(numcol INT);
CREATE INDEX t_table_idx ON t_table(numcol);
ALTER TABLE t_table ADD CONSTRAINT t_table_pk PRIMARY KEY(numcol);
SET AUTOTRACE TRACEONLY EXPLAIN
SELECT * FROM t_table WHERE numcol = 1;
Execution Plan
----------------------------------------------------------
Plan hash value: 868081059
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 13 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| T_TABLE_IDX | 1 | 13 | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("NUMCOL"=1)
INDEX(
alias index_name
)
ヒントを使用して使用する索引を指定できますが、特定の索引アクセス・パスは指定できません。
関連項目:
索引構造の詳細とBツリーの検索方法の詳細は、『Oracle Database概要』を参照してください。
INDEX
ヒントの詳細は、『Oracle Database SQL言語リファレンス』を参照してください。
このスキャンでは、指定したキーの索引を順番に検索します。索引の一意スキャンでは、2つ目のレコードがないことがわかっているため、最初のレコードが見つかるとただちに処理が停止します。データベースは索引エントリからROWIDを取得し、ROWIDで指定される行を取得します。
次の図は、索引一意スキャンを示しています。文は、主キー索引があるprod_id
列の製品IDが19
のレコードをリクエストします。
この例では、一意スキャンを使用してproducts
表から行を取得します。
次の文は、sh.products
表の製品19
のレコードを問い合せます。
SELECT * FROM sh.products WHERE prod_id = 19;
products.prod_id
列に主キー索引が存在し、WHERE
句が等価演算子を使用してすべての列を参照するため、オプティマイザは一意スキャンを選択します。
SQL_ID 3ptq5tsd5vb3d, child number 0
-------------------------------------
select * from sh.products where prod_id = 19
Plan hash value: 4047888317
--------------------------------------------------------------------------------
| Id| Operation | Name |Rows|Bytes|Cost (%CPU)|Time |
--------------------------------------------------------------------------------
| 0| SELECT STATEMENT | | | | 1 (100)| |
| 1| TABLE ACCESS BY INDEX ROWID| PRODUCTS | 1 | 173 | 1 (0)| 00:00:01|
|* 2| INDEX UNIQUE SCAN | PRODUCTS_PK | 1 | | 0 (0)| |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("PROD_ID"=19)
索引レンジ・スキャンは、値の順序付きスキャンです。
スキャンの範囲は、両側での境界スキャン、あるいは片側または両側での非有界スキャンとすることができます。オプティマイザは、通常、選択性の問合せにはレンジ・スキャンを選択します。
デフォルトでは、データベースは索引を昇順に格納し、それらのスキャンも同じ順序で行います。たとえば、述語department_id >= 20
を含む問合せにはレンジ・スキャンが使用され、20
、30
、40
といった索引キーでソートされた行が返されます。複数の索引エントリに同一のキーがある場合は、データベースはそれらをROWIDの昇順で返します。すなわち、0,AAAPvCAAFAAAAFaAAa
の次に0,AAAPvCAAFAAAAFaAAg
などとなります。
索引レンジ・スキャン降順は、行が降順で戻されること以外、索引レンジ・スキャンと同じです。通常、降順スキャンが使用されるのは、データを降順に並べる場合か、指定された値より小さい値を検索する場合です。
関連項目:
索引レンジ・スキャンでは、複数の値を索引キーにできることが必要です。
具体的には、オプティマイザは次の状況で索引レンジ・スキャンを考慮します。
索引の1つ以上の先頭列を条件に指定します。
条件は、1つ以上の式および論理(ブール)演算子の組合せを指定し、TRUE
、FALSE
またはUNKNOWN
の値を戻します。次に、条件の例を示します。
department_id = :id
department_id < :id
department_id > :id
索引の先頭列用の前述の条件のAND
組合せ(department_id > :low AND department_id < :hi
など)。
注意:
オプティマイザがレンジ・スキャンを考慮するために、col1 LIKE '%ASD'
の形式のワイルド・カード検索は先頭に指定しないでください。
0個、1個、または複数の値を索引キーにできます。
ヒント:
ソートされたデータが必要な場合は、索引には依存せず、ORDER
BY
句を使用します。索引でORDER
BY
句を満たすことが可能な場合、オプティマイザはこのオプションを使用して、ソートを回避します。
索引でORDER BY DESCENDING
句を満たすことが可能な場合、オプティマイザは索引レンジ・スキャン降順を考慮します。
オプティマイザが全表スキャンまたは別の索引を選択する場合、このアクセス・パスを強制するためにヒントが必要になる可能性があります。INDEX(
tbl_alias
ix_name
)
ヒントおよびINDEX_DESC(
tbl_alias
ix_name
)
ヒントは、特定の索引を使用するようにオプティマイザに指示します。
関連項目:
INDEX
およびINDEX_DESC
ヒントの詳細は、『Oracle Database SQL言語リファレンス』を参照してください
索引レンジ・スキャン中、Oracle Databaseはルートからブランチに進みます。
一般に、スキャン・アルゴリズムは次のとおりです。
ルート・ブロックを読み取ります。
ブランチ・ブロックを読み取ります。
すべてのデータが取り出されるまで、次の手順を交互に行います。
リーフ・ブロックを読み取って、ROWIDを取得します。
表ブロックを読み取って、行を取得します。
注意:
場合によっては、索引スキャンは索引ブロックのセットを読み取り、ROWIDをソートしてから、表ブロックのセットを読み取ります。
そのため、索引をスキャンするには、データベースではリーフ・ブロックを後方または前方に移動します。たとえば、20から40のIDを求めるスキャンでは、最小キー値20以上を含む最初のリーフ・ブロックが検索されます。40より大きい値が見つかるまで、スキャンはリンクされたリーフ・ノード・リストを水平に進み、その後停止します。
次の図は、昇順を使用した索引レンジ・スキャンを示しています。文は、department_id
列(非一意索引を含む)の値が20
のemployees
レコードをリクエストします。この例では、部門20
には2つの索引エントリが存在します。
この例では、索引レンジ・スキャンを使用してemployees
表から値のセットを取得します。
次の文は、給与が1000
より多い、部門20
の従業員のレコードを問い合せます。
SELECT * FROM employees WHERE department_id = 20 AND salary > 1000;
この問合せはカーディナリティが低い(戻される行が少ない)ため、department_id
列の索引が使用されます。データベースは索引をスキャンし、employees表からレコードをフェッチした後、salary > 1000
フィルタをフェッチしたレコードに適用して結果を生成します。
SQL_ID brt5abvbxw9tq, child number 0
-------------------------------------
SELECT * FROM employees WHERE department_id = 20 AND salary > 1000
Plan hash value: 2799965532
-------------------------------------------------------------------------------------------
|Id | Operation | Name |Rows|Bytes|Cost(%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 2 (100)| |
|*1 | TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES | 2 | 138 | 2 (0)|00:00:01|
|*2 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX| 2 | | 1 (0)|00:00:01|
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("SALARY">1000)
2 - access("DEPARTMENT_ID"=20)
この例では、索引を使用して、employees
表からソートされた順序で行を取得します。
次の文は、部門20
の従業員のレコードを降順で問い合せます。
SELECT * FROM employees WHERE department_id < 20 ORDER BY department_id DESC;
この問合せはカーディナリティが低いため、department_id
列の索引が使用されます。
SQL_ID 8182ndfj1ttj6, child number 0
-------------------------------------
SELECT * FROM employees WHERE department_id < 20 ORDER BY department_id DESC
Plan hash value: 1681890450
--------------------------------------------------------------------------------
|Id| Operation | Name |Rows|Bytes|Cost(%CPU)|Time |
--------------------------------------------------------------------------------
| 0| SELECT STATEMENT | | | | 2 (100)| |
| 1| TABLE ACCESS BY INDEX ROWID | EMPLOYEES | 2|138| 2 (0)|00:00:01|
|*2| INDEX RANGE SCAN DESCENDING| EMP_DEPARTMENT_IX | 2| | 1 (0)|00:00:01|
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("DEPARTMENT_ID"<20)
データベースは、20
以下の最大キー値を含む、最初の索引リーフ・ブロックの位置を特定します。次に、スキャンはリンクされたリーフ・ノード・リストを左方向に水平に進みます。データベースは各索引エントリからROWIDを取得し、ROWIDで指定される行を取得します。
全索引スキャンは、索引全体を順に読み取ります。索引のデータは索引キー順に並んでいるため、全索引スキャンで個別のソート操作を排除できます。
オプティマイザは様々な状況で全索引スキャンを考慮します。
状況には、次のようなものがあります。
述語が索引の列を参照している。この列は先頭列である必要はありません。
述語は指定されていないが、次のすべての条件は満たしている。
表および問合せのすべての列は索引にある。
少なくとも1つの索引付き列がNULLではない。
問合せに、索引付きのNULL不可能列に対するORDER BY
が含まれる。
データベースはルート・ブロックを読み取り、リーフ・ブロックに到達するまで索引の左方向(または、降順全スキャンを行っている場合は右)に移動します。
データベースがリーフ・ブロックに到達すると、スキャンは一度に1ブロックずつ、ソートされた順序で下位の索引に進みます。データベースは、マルチブロックI/Oではなく単一ブロックI/Oを使用します。
次の図は、全索引スキャンを示しています。文は、department_id
で並べられているdepartments
レコードをリクエストします。
この例では、全索引スキャンを使用してORDER BY
句で問合せを満たします。
次の文は、部門のIDと名前を部門ID順に問い合せます。
SELECT department_id, department_name FROM departments ORDER BY department_id;
次の計画は、オプティマイザが全索引スキャンを選択することを示しています。
SQL_ID 94t4a20h8what, child number 0
-------------------------------------
select department_id, department_name from departments order by department_id
Plan hash value: 4179022242
--------------------------------------------------------------------------------
|Id | Operation | Name |Rows|Bytes|Cost(%CPU)|Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 2 (100)| |
| 1 | TABLE ACCESS BY INDEX ROWID| DEPARTMENTS | 27 | 432 | 2 (0)| 00:00:01 |
| 2 | INDEX FULL SCAN | DEPT_ID_PK | 27 | | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------
データベースは最初の索引リーフ・ブロックを特定し、リンクされたリーフ・ノード・リストを右方向に水平に進みます。各索引エントリについて、データベースはエントリからROWIDを取得し、ROWIDで指定される表の行を取得します。索引はdepartment_id
でソートされるため、データベースは取得した行の個別のソート操作を回避します。
高速全索引スキャンは、ソートされていない索引ブロックをディスク上に存在しているとおりに読み取ります。このスキャンは表を調べるために索引を使用しませんが、基本的には索引自体を表のように使用しながら、表ではなく索引を読み取ります。
問合せが索引の属性のみにアクセスする場合、オプティマイザはこのスキャンを考慮します。
注意:
全スキャンとは異なり、高速全スキャンではソート操作を排除できません。これは索引を順番に読み取らないためです。
INDEX_FFS(table_name index_name)
ヒントにより高速全索引スキャンが強制されます。
関連項目:
INDEX
ヒントの詳細は、『Oracle Database SQL言語リファレンス』を参照してください。
データベースは、マルチブロックI/Oを使用して、ルート・ブロックと、すべてのリーフ・ブロックおよびブランチ・ブロックを読み取ります。データベースは、ブランチ・ブロックとルート・ブロックを無視して、リーフ・ブロックの索引エントリを読み取ります。
この例では、オプティマイザ・ヒントの結果として高速全索引スキャンを使用します。
次の文は、部門のIDと名前を部門ID順に問い合せます。
SELECT /*+ INDEX_FFS(departments dept_id_pk) */ COUNT(*) FROM departments;
次の計画は、オプティマイザが高速全索引スキャンを選択することを示しています。
SQL_ID fu0k5nvx7sftm, child number 0
-------------------------------------
select /*+ index_ffs(departments dept_id_pk) */ count(*) from departments
Plan hash value: 3940160378
--------------------------------------------------------------------------
| Id | Operation | Name | Rows |Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | 2 (100)| |
| 1 | SORT AGGREGATE | | 1 | | |
| 2 | INDEX FAST FULL SCAN| DEPT_ID_PK | 27 | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------
索引スキップ・スキャンは、コンポジット索引の初期列が問合せでスキップされる(指定されない)場合に行われます。
関連項目:
『Oracle Database概要』
多くの場合、索引ブロックをスキャンするほうが、表データ・ブロックをスキャンしたり全索引スキャンを実行するより高速です。
次の基準が当てはまる場合、オプティマイザはスキップ・スキャンを考慮します。
コンポジット索引の先頭列が問合せ述語で指定されていない。
たとえば、問合せ述語がcust_gender
列を参照せず、コンポジット索引キーが(cust_gender,cust_email)
の場合です。
コンポジット索引の先頭列に個別値がほとんどなく、索引の非先頭キーに個別値が多数存在する。
たとえば、コンポジット索引キーが(cust_gender,cust_email)
である場合、cust_gender
列には個別値が2つしか存在しませんが、cust_email
には数千存在します。
索引スキップ・スキャンは、コンポジット索引をさらに小さい副索引に論理的に分割します。
論理副索引の個数は、索引の先頭列にある個別値の数によって判断されます。個別値の数が少ないほど、オプティマイザが作成しなければならない論理副索引の数が少なくなり、スキャンはより効率的になります。スキャンは各論理索引を個別に読み取り、非先頭列に対するフィルタ条件に一致しない索引ブロックをスキップします。
この例では、索引スキップ・スキャンを使用してcustomers
表の問合せを満たします。
customers
表にはcust_gender
という列があり、この列の値はM
またはF
です。次のとおり、列(cust_gender、cust_email
)にコンポジット索引を作成します。
CREATE INDEX cust_gender_email_ix ON sh.customers (cust_gender, cust_email);
概念上、索引の一部は、索引の先頭として性別の値にF
またはM
を持つ次のような形式になります。
F,Wolf@company.example.com,rowid F,Wolsey@company.example.com,rowid F,Wood@company.example.com,rowid F,Woodman@company.example.com,rowid F,Yang@company.example.com,rowid F,Zimmerman@company.example.com,rowid M,Abbassi@company.example.com,rowid M,Abbey@company.example.com,rowid
sh.customers
表の顧客に対して次の問合せを実行します。
SELECT * FROM sh.customers WHERE cust_email = 'Abbey@company.example.com';
cust_gender
はWHERE
句で指定されていませんが、データベースでは、customers_gender_email
索引のスキップ・スキャンを使用できます。サンプルの索引では、先頭列cust_gender
に可能な値は2つ(F
およびM
)あります。データベースは索引を論理的に2つに分割します。1つ目の副索引は、次の形式のエントリで、キーF
を持ちます。
F,Wolf@company.example.com,rowid F,Wolsey@company.example.com,rowid F,Wood@company.example.com,rowid F,Woodman@company.example.com,rowid F,Yang@company.example.com,rowid F,Zimmerman@company.example.com,rowid
2つ目の副索引は、次の形式のエントリで、キーM
を持ちます。
M,Abbassi@company.example.com,rowid M,Abbey@company.example.com,rowid
電子メールがAbbey@company.com
の顧客のレコードを検索する場合、最初に先頭値F
を持つ副索引が検索され、次に先頭値M
を持つ副索引が検索されます。概念上、データベースで問合せが次のように処理されます。
( SELECT * FROM sh.customers WHERE cust_gender = 'F' AND cust_email = 'Abbey@company.com' ) UNION ALL ( SELECT * FROM sh.customers WHERE cust_gender = 'M' AND cust_email = 'Abbey@company.com' )
問合せの計画は次のようになります。
SQL_ID d7a6xurcnx2dj, child number 0
-------------------------------------
SELECT * FROM sh.customers WHERE cust_email = 'Abbey@company.example.com'
Plan hash value: 797907791
-----------------------------------------------------------------------------------------
|Id| Operation | Name |Rows|Bytes|Cost(%CPU)|Time|
-----------------------------------------------------------------------------------------
| 0|SELECT STATEMENT | | | |10(100)| |
| 1| TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMERS |33|6237| 10(0)|00:00:01|
|*2| INDEX SKIP SCAN | CUST_GENDER_EMAIL_IX |33| | 4(0)|00:00:01|
-----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("CUST_EMAIL"='Abbey@company.example.com')
filter("CUST_EMAIL"='Abbey@company.example.com')
関連項目:
スキップ・スキャンの詳細は、『Oracle Database概要』を参照してください。
索引結合スキャンは、問合せによってリクエストされたすべての列をまとめて返す、複数の索引のハッシュ結合です。すべてのデータは索引から取得されるため、データベースは表にアクセスする必要がありません。
場合によっては、表アクセスの回避は最もコスト効率がよいオプションとなります。
オプティマイザは、次の状況で索引結合を考慮します。
表アクセスの必要なしに、問合せでリクエストされたすべてのデータを複数索引のハッシュ結合によって取得する。
表から行を取得するコストは、表から行を取得せずに索引を読み込む場合よりも高い。索引結合はコストが高くなることがよくあります。たとえば、2つの索引をスキャンして結合する場合、最も選択的な索引を選択し、表を調べるほうがコストがかかりません。
INDEX_JOIN(table_name)
ヒントを使用して索引結合を指定できます。
関連項目:
『Oracle Database SQL言語リファレンス』
索引結合では複数の索引がスキャンされ、そのスキャンから取得したROWIDでのハッシュ結合を使用して行を返します。
索引結合スキャンでは、表アクセスは常に回避されます。たとえば、1つの表の2つの索引を結合する処理は次のとおりです。
最初の索引をスキャンしてROWIDを取得します。
2番目の索引をスキャンしてROWIDを取得します。
ROWIDでのハッシュ結合を実行し、行を取得します。
次の文では、索引結合を指定して、姓がA
で始まる従業員の姓、電子メールを問合せます。
SELECT /*+ INDEX_JOIN(employees) */ last_name, email FROM employees WHERE last_name like 'A%';
個別の索引が、(
last_name
,first_name)
列およびemail
列に存在します。emp_name_ix
索引の一部は次のような形式です。
Banda,Amit,AAAVgdAALAAAABSABD Bates,Elizabeth,AAAVgdAALAAAABSABI Bell,Sarah,AAAVgdAALAAAABSABc Bernstein,David,AAAVgdAALAAAABSAAz Bissot,Laura,AAAVgdAALAAAABSAAd Bloom,Harrison,AAAVgdAALAAAABSABF Bull,Alexis,AAAVgdAALAAAABSABV
emp_email_uk
索引の最初の部分は次のような形式です。
ABANDA,AAAVgdAALAAAABSABD ABULL,AAAVgdAALAAAABSABV ACABRIO,AAAVgdAALAAAABSABX AERRAZUR,AAAVgdAALAAAABSAAv AFRIPP,AAAVgdAALAAAABSAAV AHUNOLD,AAAVgdAALAAAABSAAD AHUTTON,AAAVgdAALAAAABSABL
例8-4では、DBMS_XPLAN.DISPLAY_CURSOR
関数を使用して計画を取得します。データベースはemp_email_uk
索引のすべてのROWIDを取得し、A
で始まる姓のemp_name_ix
のROWIDを取得します。データベースはハッシュ結合を使用して、ROWIDの両方のセットから一致するものを探します。たとえば、ROWID AAAVgdAALAAAABSABD
は両方のセットのROWIDに発生するので、データベースはこのROWIDに対応するレコードをemployees
表から探します。
例8-4 索引結合スキャン
SQL_ID d2djchyc9hmrz, child number 0 ------------------------------------- SELECT /*+ INDEX_JOIN(employees) */ last_name, email FROM employees WHERE last_name like 'A%' Plan hash value: 3719800892 ------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 3 (100)| | |* 1 | VIEW | index$_join$_001 | 3 | 48 | 3 (34)| 00:00:01 | |* 2 | HASH JOIN | | | | | | |* 3 | INDEX RANGE SCAN | EMP_NAME_IX | 3 | 48 | 1 (0)| 00:00:01 | | 4 | INDEX FAST FULL SCAN| EMP_EMAIL_UK | 3 | 48 | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("LAST_NAME" LIKE 'A%') 2 - access(ROWID=ROWID) 3 - access("LAST_NAME" LIKE 'A%')
ビットマップ索引では、索引付けられたデータをROWID範囲と組み合せます。
この項では、ビットマップ索引、およびより頻繁に使われるビットマップ索引アクセス・パスについて説明します。
従来型のBツリー索引では、1つの索引エントリは単一の行を指します。ビットマップ索引では、キーは索引付けられたデータとROWID範囲の組合せです。
データベースでは、索引キーごとに1つ以上のビットマップが格納されます。1
および0
が連続するビットマップの各値は、ROWID範囲内の行を指します。したがって、ビットマップ索引では、索引エントリは単一の行ではなく行のセットを指します。
ビットマップ索引はBツリー索引とは異なるキーを使用しますが、Bツリー構造で格納されます。
次の表では、索引エントリの種類における違いを示します。
表8-3 Bツリーおよびビットマップの索引エントリ
索引エントリ | キー | データ | 例 |
---|---|---|---|
一意のBツリー |
索引付けられたデータのみ |
ROWID |
101,AAAPvCAAFAAAAFaAAa |
一意ではないBツリー |
ROWIDと組み合せた索引付けられたデータ |
なし |
Smith,AAAPvCAAFAAAAFaAAa |
ビットマップ |
ROWID範囲と組み合せた索引付けられたデータ |
ビットマップ |
M,low-rowid,high-rowid,1000101010101010 |
データベースでは、ビットマップ索引をBツリー構造に格納します。データベースでは、索引が定義される属性のセットであるキーの最初の部分のBツリーが即座に検索され、対応するROWID範囲とビットマップが取得されます。
関連項目:
ビットマップ索引の概要は、『Oracle Database概要』を参照してください。
ビットマップ索引の詳細は、『Oracle Databaseデータ・ウェアハウス・ガイド』を参照してください。
ビットマップ索引は、頻繁に変更されない個別のカーディナリティが低いデータに適しています。行の総数に対して列の個別値の数が少ない場合は、データのカーディナリティは低くなります。
一般に、Bツリー索引はカーディナリティが高いデータに適しています。オプティマイザは、ほとんど行を返さない問合せにBツリー索引を選択します。対照的に、ビットマップ索引は個別のカーディナリティが低いデータに適しています。customers.cust_gender
列は、2つの個別値(M
およびF
)とnullのみのため、ビットマップ索引の候補です。表に1億行が含まれる場合、全女性顧客に対する問合せは非選択性の問合せであるため、ビットマップ索引アクセスの候補です。ビットマップ索引は、圧縮技法によって最小限のI/Oで多数のROWIDを生成できます。
ビットマップ索引は、データ・ウェアハウス内の非定型問合せを高速化するために有用な方法です。特に、ビットマップ索引は次に該当する問合せで有用なアクセス・パスです。
WHERE
句に複数の条件がある
すべての条件ではなく一部の条件のみを満たす行は、表自体がアクセスされる前にデータベースによって除外されます。
カーディナリティが低い列でAND
、OR
およびNOT
演算がある
カーディナリティの低い列でビットマップ索引を組み合せると、これらの演算の効率が高くなります。データベースでは、ビットマップ索引から即座にビットマップをマージできます。たとえば、ビットマップ索引がcustomers
のcust_gender
列およびcust_marital_status
列にある場合、これらの索引により次の問合せのパフォーマンスが大幅に向上します。
SELECT * FROM customers WHERE cust_gender = 'M' AND cust_marital_status = 'single'
データベースでは、マージされたビットマップ内の1
の値を効率的にROWIDに変換できます。
COUNT
関数
データベースでは、表をスキャンすることなくビットマップ索引をスキャンできます。
NULL値を選択する述語
Bツリー索引と異なり、ビットマップ索引にNULLを含めることができます。列のnull数をカウントする問合せでは、表をスキャンせずにビットマップ索引を使用できます。
ビットマップ索引は、DMLが頻繁に使用される表には有用ではありません。理由は、1つの索引キーが多数の行を指しているためです。索引付けられたデータがセッションで変更される場合、データベースはビットマップ内の単一ビットをロックできず、索引エントリ全体をロックします。これにより、実際にはビットマップによりポイントされた行をロックします。たとえば、特定の顧客の婚姻ステータスがsingle
からmarried
に変化した場合、データベースはビットマップ内のsingle
索引エントリおよびmarried
索引エントリに対する排他的アクセスを取得する必要があります。これらの2つの値を含む行は、COMMIT
まで変更できません。
関連項目:
COUNT
関数の詳細は、『Oracle Database SQL言語リファレンス』を参照してください。
ビットマップの各値は、行の値がビットマップ条件に一致する場合は1
、一致しない場合は0
になります。この値に基づいて、データベースの内部アルゴリズムによってビットマップがROWIDにマップされます。
ビットマップ・エントリには、索引付けられた値、ROWID範囲(開始ROWIDと終了ROWID)、およびビットマップが含まれます。ビットマップの0
または1
の各値は、ROWID範囲内のオフセットであり、行が存在しない場合でも、表の潜在行にマップされます。ブロック内の潜在行数は定義済のため、データベースでは、範囲のエンドポイントを使用して、範囲内の任意の行のROWIDを決定できます。
注意:
HAKAN要素は、Oracle Databaseによって単一ブロックに保存可能と見なされる行数を制限するために、ビットマップ索引アルゴリズムによって使用される最適化です。人為的に行数を制限することで、データベースでビットマップのサイズが削減されます。
表8-4は、NULL値可能なsh.customers.cust_marital_status
列のサンプル・ビットマップの一部を示しています。実際の索引には12個の個別値があります。このサンプルでは、NULL、married
、およびsingle
の3つのみを示します。
表8-4 ビットマップ索引エントリ
cust_marital_statusの列値 | 範囲の開始ROWID | 範囲の終了ROWID | 範囲の最初の行 | 範囲の2番目の行 | 範囲の3番目の行 | 範囲の4番目の行 | 範囲の5番目の行 | 範囲の6番目の行 |
---|---|---|---|---|---|---|---|---|
(null) |
|
|
0 |
0 |
0 |
0 |
0 |
1 |
|
|
|
1 |
0 |
1 |
1 |
1 |
0 |
|
|
|
0 |
1 |
0 |
0 |
0 |
0 |
|
|
|
1 |
0 |
1 |
0 |
1 |
1 |
表8-4のように、Bツリー索引とは異なり、ビットマップ索引は、完全にNULL値からなるキーを含む場合があります。表8-4では、NULLにおいて範囲内の6番目の行の値が1
です。これは、範囲内の6番目の行は、cust_marital_status
値がNULLであることを意味します。NULLの索引作成は、集計関数COUNT
による問合せなど、一部のSQL文に使用できます。
関連項目:
『Oracle Database概要』には、ROWID形式が説明されています。
ビットマップ結合索引は、2つ以上の表を結合するためのビットマップ索引です。
オプティマイザでビットマップ結合索引が使用され、計画実行中に結合する必要のあるデータの量が削減されるか、なくなります。ビットマップ結合索引は、保存時にマテリアライズド結合ビューよりも効率が大幅に向上します。
次の例では、sh.sales
表およびsh.customers
表にビットマップ索引が作成されます。
CREATE BITMAP INDEX cust_sales_bji ON sales(c.cust_city) FROM sales s, customers c WHERE c.cust_id = s.cust_id LOCAL;
先行のCREATE
文のFROM
句およびWHERE
句は、表間の結合条件を示しています。customers.cust_city
列が索引キーです。
索引の各キー値は、customers
表に存在する可能性のある市を示しています。概念的には、索引のキー値は、1つのビットマップが各キー値に関連付けられ、次のようになります。
San Francisco 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 . . . San Mateo 0 1 0 0 0 0 0 0 1 0 0 0 0 0 1 . . . Smithville 1 0 0 0 1 0 0 1 0 0 1 0 1 0 0 . . . . . .
ビットマップの各ビットは、sales
表の1つの行に対応します。Smithville
キーでは、値が1
であると、sales
表の最初の行がSmithvilleの顧客に販売された製品に対応します。値が0
であると、2番目の行がSmithvilleの顧客に販売された製品に対応します。
Smithvilleの顧客への個別販売数に関する次の問合せを考慮します。
SELECT COUNT (*) FROM sales s, customers c WHERE c.cust_id = s.cust_id AND c.cust_city = 'Smithville';
次の計画では、データベースでSmithville
ビットマップが読み取られ、Smithvilleの販売数が抽出されます(手順4)。これにより、customers
とsales
表の結合はなくなります。
SQL_ID 57s100mh142wy, child number 0
-------------------------------------
SELECT COUNT (*) FROM sales s, customers c WHERE c.cust_id = s.cust_id
AND c.cust_city = 'Smithville'
Plan hash value: 3663491772
------------------------------------------------------------------------------------
|Id| Operation | Name |Rows|Bytes|Cost (%CPU)| Time|Pstart|Pstop|
------------------------------------------------------------------------------------
| 0| SELECT STATEMENT | | | |29 (100)| | | |
| 1| SORT AGGREGATE | | 1 | 5| | | | |
| 2| PARTITION RANGE ALL | | 1708|8540|29 (0)|00:00:01|1|28|
| 3| BITMAP CONVERSION COUNT | | 1708|8540|29 (0)|00:00:01| | |
|*4| BITMAP INDEX SINGLE VALUE|CUST_SALES_BJI| | | | |1|28|
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("S"."SYS_NC00008$"='Smithville')
関連項目:
CREATE INDEX
文の詳細は、『Oracle Database概要』を参照してください。
ビットマップ索引はBツリー構造で存在し、Bツリーと同じようにブランチ・ブロックおよびリーフ・ブロックを使用します。
たとえば、customers.cust_marital_status
列に12の個別値がある場合、1つのブランチ・ブロックがmarried,rowid-range
キーおよびsingle,rowid-range
キーを指し、別のブランチ・ブロックがwidowed,rowid-range
キーを指すといった状況があります。または、単一のブランチ・ブロックが12の個別キーすべてを含むリーフ・ブロックを指す場合もあります。
索引付けられた列値には、それぞれ1つ以上のビットマップ・ピースがあり、それぞれに1つ以上のエクステントの一連の近接行を占める独自のROWID範囲があります。データベースはビットマップ・ピースを使用し、ブロックのサイズに比べて大きい索引エントリを分割できます。たとえば、データベースは単一の索引エントリが3つのピースに分割し、最初の2ピースが同じエクステントの個別ブロック、最後のピースが別のエクステントの個別ブロックに分割できます。
領域を節約するために、Oracle Databaseでは連続した範囲の0
値を圧縮できます。
ビットマップ変換は、ビットマップのエントリと表の行の間で変換されます。この変換は、エントリから行(TO ROWID
)、または行からエントリ(FROM ROWID
)へと変換できます。
この例では、オプティマイザは範囲述語を使用した問合せを満たすためにビットマップの変換操作を選択します。
sh.customers
表の問合せは、1918年より前に生まれたすべての顧客の名前を選択します。
SELECT cust_last_name, cust_first_name FROM customers WHERE cust_year_of_birth < 1918;
次の計画では、データベースがレンジ・スキャンを使用して1918
より小さいすべてのキー値を検索し(手順3)、ビットマップの1
の値をROWIDに変換し(手順2)、次に、ROWIDを使用してcustomers
表から行を取得します(手順1)。
---------------------------------------------------------------------------------------------
|Id| Operation | Name |Rows|Bytes|Cost(%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT | | | |421 (100)| |
| 1| TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMERS |3604|68476|421 (1)| 00:00:01 |
| 2| BITMAP CONVERSION TO ROWIDS | | | | | |
|*3| BITMAP INDEX RANGE SCAN | CUSTOMERS_YOB_BIX| | | | |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("CUST_YEAR_OF_BIRTH"<1918)
filter("CUST_YEAR_OF_BIRTH"<1918)
この種類のアクセス・パスは、ビットマップ索引を使用して単一のキー値を検索します。
問合せは単一ビットマップをスキャンして1
の値を含む位置を調べます。データベースは1
の値をROWIDに変換し、そのROWIDを使用して行を検索します。
データベースは単一のビットマップを処理することのみが必要です。たとえば、次の表はsh.customers.cust_marital_status
列内の値widowed
に対するビットマップ索引(2つのビットマップ・ピース)を表しています。ステータスがwidowed
の顧客の問合せを満たすために、データベースはwidowed
ビットマップ内の1
の値をそれぞれ検索し、対応する行のROWIDを見つけることができます。
表8-5 ビットマップ索引エントリ
列値 | 範囲の開始ROWID | 範囲の終了ROWID | 範囲の最初の行 | 範囲の2番目の行 | 範囲の3番目の行 | 範囲の4番目の行 | 範囲の5番目の行 | 範囲の6番目の行 |
---|---|---|---|---|---|---|---|---|
|
|
|
0 |
1 |
0 |
0 |
0 |
0 |
|
|
|
1 |
0 |
1 |
0 |
1 |
1 |
この例では、オプティマイザは等価述語を使用した問合せを満たすためにビットマップ索引の単一値演算を選択します。
sh.customers
表の問合せは、すべてのwidowed顧客を選択します。
SELECT * FROM customers WHERE cust_marital_status = 'Widowed';
次の計画では、データベースがcustomers
ビットマップ索引のWidowed
キーのエントリを読み取り(手順3)、ビットマップの1
の値をROWIDに変換し(手順2)、次に、ROWIDを使用してcustomers
表から行を取得します(手順1)。
SQL_ID ff5an2xsn086h, child number 0
-------------------------------------
SELECT * FROM customers WHERE cust_marital_status = 'Widowed'
Plan hash value: 2579015045
---------------------------------------------------------------------------------------------
|Id| Operation | Name |Rows|Bytes|Cost (%CPU)| Time|
---------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT | | | |412 (100)| |
| 1| TABLE ACCESS BY INDEX ROWID BATCHED|CUSTOMERS |3461|638K|412 (2)|00:00:01|
| 2| BITMAP CONVERSION TO ROWIDS | | | | | |
|*3| BITMAP INDEX SINGLE VALUE |CUSTOMERS_MARITAL_BIX| | | | |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("CUST_MARITAL_STATUS"='Widowed')
この種類のアクセス・パスは、ビットマップ索引を使用して値の範囲を検索します。
述語で値の範囲が選択される場合に、オプティマイザはこの種類のアクセス・パスを検討します。
スキャンの範囲は、両側での境界スキャン、あるいは片側または両側での非有界スキャンとすることができます。オプティマイザは、通常、選択性の問合せにはレンジ・スキャンを選択します。
関連項目:
このスキャンは、Bツリーのレンジ・スキャンと同じように機能します。
たとえば、次の表はsh.customers.cust_year_of_birth
列に対するビットマップ索引の3つの値を表しています。問合せで1917年より前に生まれたすべての顧客をリクエストしている場合、データベースはこの索引で1917
より低い値をスキャンして、1
を持つ行のROWIDを取得します。
表8-6 ビットマップ索引エントリ
列値 | 範囲の開始ROWID | 範囲の終了ROWID | 範囲の最初の行 | 範囲の2番目の行 | 範囲の3番目の行 | 範囲の4番目の行 | 範囲の5番目の行 | 範囲の6番目の行 |
---|---|---|---|---|---|---|---|---|
|
|
|
0 |
0 |
0 |
0 |
0 |
1 |
|
|
|
1 |
0 |
1 |
1 |
1 |
0 |
|
|
|
0 |
1 |
0 |
0 |
0 |
0 |
|
|
|
1 |
0 |
1 |
0 |
1 |
1 |
関連項目:
この例では、レンジ・スキャンを使用して単一の年より前に生まれた顧客を選択します。
sh.customers
表の問合せは、1918年より前に生まれた顧客の名前を選択します。
SELECT cust_last_name, cust_first_name FROM customers WHERE cust_year_of_birth < 1918
次の計画では、1918
より小さいcust_year_of_birth
キーのビットマップをすべて取得し(手順3)、ビットマップをROWIDに変換し(手順2)次に行を取得します(手順1)。
SQL_ID 672z2h9rawyjg, child number 0
-------------------------------------
SELECT cust_last_name, cust_first_name FROM customers WHERE
cust_year_of_birth < 1918
Plan hash value: 4198466611
---------------------------------------------------------------------------------------------
|Id| Operation | Name |Rows|Bytes|Cost(%CPU)|Time |
---------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT | | | |421 (100)| |
| 1| TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMERS |3604|68476|421 (1)|00:00:01 |
| 2| BITMAP CONVERSION TO ROWIDS | | | | | |
|*3| BITMAP INDEX RANGE SCAN | CUSTOMERS_YOB_BIX | | | | |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("CUST_YEAR_OF_BIRTH"<1918)
filter("CUST_YEAR_OF_BIRTH"<1918)
このアクセス・パスは複数のビットマップを結合し、単一のビットマップを結果として返します。
ビットマップ・マージが、実行計画のBITMAP MERGE
操作により示されています。
結合では、2つのビットマップ間でブールOR
演算が使用されます。結果のビットマップでは、最初のビットマップのすべての行が選択され、さらに後続のすべてのビットマップのすべての行も選択されます。
問合せが1918年より前に生まれたすべての顧客を選択する場合があります。次の例では、3つのcustomers.cust_year_of_birth
キー1917
、1916
、および1915
のサンプル・ビットマップを示します。ビットマップのいずれかの位置に1
があれば、結合ビットマップの同じ位置が1
になります。それ以外の場合は、結合ビットマップは0
になります。
1917 1 0 1 0 0 0 0 0 0 0 0 0 0 1 1916 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1915 0 0 0 0 0 0 0 0 1 0 0 0 0 0 ------------------------------------ merged: 1 1 1 0 0 0 0 0 1 0 0 0 0 1
結果のビットマップにある1
の値が、1915
、1916
または1917
の値を含む行に対応します。
この例は、範囲述語を使用した問合せを最適化するためにデータベースがビットマップをマージする方法を示しています。
sh.customers
表の問合せは、1918年より前に生まれた女性の顧客の名前を選択します。
SELECT cust_last_name, cust_first_name FROM customers WHERE cust_gender = 'F' AND cust_year_of_birth < 1918
次の計画では、1918
より小さいcust_year_of_birth
キーのビットマップをすべて取得し(手順6)、これらのビットマップをOR
ロジックを使用してマージして単一のビットマップを作成します(手順5)。データベースは、F
のcust_gender
キーに対する単一のビットマップを取得し(手順4)、これらの2つのビットマップにAND
演算を実行します。結果は、リクエストされた行に対して1
の値を含む単一のビットマップです(手順3)。
SQL_ID 1xf59h179zdg2, child number 0
-------------------------------------
select cust_last_name, cust_first_name from customers where cust_gender
= 'F' and cust_year_of_birth < 1918
Plan hash value: 49820847
---------------------------------------------------------------------------------------------
|Id| Operation | Name |Rows|Bytes|Cost(%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT | | | |288 (100)| |
| 1| TABLE ACCESS BY INDEX ROWID BATCHED|CUSTOMERS |1802|37842|288 (1)|00:00:01|
| 2| BITMAP CONVERSION TO ROWIDS | | | | | |
| 3| BITMAP AND | | | | | |
|*4| BITMAP INDEX SINGLE VALUE |CUSTOMERS_GENDER_BIX| | | | |
| 5| BITMAP MERGE | | | | | |
|*6| BITMAP INDEX RANGE SCAN |CUSTOMERS_YOB_BIX | | | | |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("CUST_GENDER"='F')
6 - access("CUST_YEAR_OF_BIRTH"<1918)
filter("CUST_YEAR_OF_BIRTH"<1918)
表クラスタとは、共通の列を共有し、関連するデータを同じブロックに格納する表のグループのことです。表がクラスタ化されると、1つのデータ・ブロックに複数の表の行を含めることができます。
この項の内容は次のとおりです。
関連項目:
表クラスタの概要は、『Oracle Database概要』を参照してください。
索引クラスタとは、索引を使用してデータを検索する表クラスタのことです。
クラスタ索引とは、クラスタ・キーのBツリー索引のことです。クラスタ・スキャンでは、索引クラスタに格納された表から、同じクラスタ・キー値を持つすべての行が取得されます。
索引クラスタでは、同じクラスタ・キー値を持つすべての行が同じデータ・ブロックに格納されます。
たとえば、hr.employees2
表およびhr.departments2
表がemp_dept_cluster
でクラスタ化され、クラスタ・キーがdepartment_id
の場合、データベースに格納されるのは、同じブロックの部門10
のすべての従業員、同じブロックの部門20
のすべての従業員などになります。
Bツリー・クラスタ索引は、データを含むブロックのデータベース・ブロック・アドレス(DBA)にクラスタ・キー値を関連付けます。たとえば、キー30
の索引エントリには、部門30
の従業員の行を含むブロックのアドレスが表示されます。
30,AADAAAA9d
ユーザーがクラスタの行を要求すると、データベースは索引をスキャンしてその行を含むブロックのDBAを取得します。次に、このDBAに基づいて行の位置を特定します。
この例では、department_id
列でemployees
表とdepartments
表をクラスタ化し、単一部門のクラスタを問い合せます。
ユーザーhr
として、表クラスタ、クラスタ索引、およびクラスタの表を次のように作成します。
CREATE CLUSTER employees_departments_cluster (department_id NUMBER(4)) SIZE 512; CREATE INDEX idx_emp_dept_cluster ON CLUSTER employees_departments_cluster; CREATE TABLE employees2 CLUSTER employees_departments_cluster (department_id) AS SELECT * FROM employees; CREATE TABLE departments2 CLUSTER employees_departments_cluster (department_id) AS SELECT * FROM departments;
部門30
の従業員を次のように問い合せます。
SELECT * FROM employees2 WHERE department_id = 30;
このスキャンでは、Oracle Databaseは最初にクラスタ索引をスキャンし、部門30を示す行のROWIDを取得します(手順2)。次に、このROWIDを使用してemployees2
の行の位置を特定します(手順1)。
SQL_ID b7xk1jzuwdc6t, child number 0
-------------------------------------
SELECT * FROM employees2 WHERE department_id = 30
Plan hash value: 49826199
--------------------------------------------------------------------------------
|Id| Operation | Name |Rows|Bytes|Cost(%CPU)| Time |
--------------------------------------------------------------------------------
| 0| SELECT STATEMENT | | | | 2 (100)| |
| 1| TABLE ACCESS CLUSTER| EMPLOYEES2 | 6 | 798 | 2 (0)| 00:00:01|
|*2| INDEX UNIQUE SCAN |IDX_EMP_DEPT_CLUSTER| 1 | | 1 (0)| 00:00:01|
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("DEPARTMENT_ID"=30)
関連項目:
索引付きクラスタの詳細は、『Oracle Database概要』を参照してください。
ハッシュ・クラスタは、索引キーがハッシュ関数に置き換わることを除いて、索引付きクラスタと似ています。別個のクラスタ索引は存在しません。
ハッシュ・クラスタでは、データが索引になります。ハッシュ・スキャンでは、ハッシュ値に基づいてハッシュ・クラスタ内の行の位置を特定します。
ハッシュ・クラスタ内においては、同一のハッシュ値を持つすべての行が同じデータ・ブロックに格納されています。
クラスタのハッシュ・スキャンを実行するには、最初に、文で指定されたクラスタ・キー値にハッシュ関数を適用することによって、ハッシュ値を取得します。次に、Oracle Databaseはこのハッシュ値の行を含むデータ・ブロックをスキャンします。
この例では、department_id
列でemployees
表とdepartments
表をハッシュし、単一部門のクラスタを問い合せます。
ハッシュ・クラスタおよびクラスタの表を次のように作成します。
CREATE CLUSTER employees_departments_cluster (department_id NUMBER(4)) SIZE 8192 HASHKEYS 100; CREATE TABLE employees2 CLUSTER employees_departments_cluster (department_id) AS SELECT * FROM employees; CREATE TABLE departments2 CLUSTER employees_departments_cluster (department_id) AS SELECT * FROM departments;
部門30
の従業員を次のように問い合せます。
SELECT * FROM employees2 WHERE department_id = 30
ハッシュ・スキャンを実行するには、最初に、キー値30
にハッシュ関数を適用することによって、ハッシュ値を取得し、次に、そのハッシュ値を使用してデータ・ブロックをスキャンし、行を取得します(手順1)。
SQL_ID 919x7hyyxr6p4, child number 0
-------------------------------------
SELECT * FROM employees2 WHERE department_id = 30
Plan hash value: 2399378016
----------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
----------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 1 |
|* 1 | TABLE ACCESS HASH| EMPLOYEES2 | 10 | 1330 | |
----------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("DEPARTMENT_ID"=30)
関連項目:
ハッシュ・クラスタの詳細は、『Oracle Database概要』を参照してください。