8 オプティマイザのアクセス・パス

アクセス・パスは、問合せにより行ソースから行を取得するために使用される手法です。

この章のトピックは、次のとおりです:

8.1 「アクセス・パスの概要」

行ソースは、実行計画のステップによって戻される行のセットです。表、ビュー、または結合処理あるいはグループ化処理の結果が行ソースになる可能性があります。

アクセス・パスなどの単項演算は、行ソースから行を取得するために問合せで使用される手法であり、単一の行ソースを入力として受け入れます。たとえば、全表スキャンは、単一行ソースの行の取得です。対照的に、結合操作はバイナリで、2つの行ソースから入力を受け取ります。

データベースは、異なるリレーショナル・データ構造に対して異なるアクセス・パスを使用します。次の表は、主なデータ構造に対する一般的なアクセス・パスについてまとめたものです。

オプティマイザは、使用可能な様々な実行計画を検討し、各計画にコストを割り当てます。オプティマイザは、コストが最も低い計画を選択します。一般に、表の行の小さいサブセットを取得する文には索引アクセス・パスのほうが効率的ですが、表の大きな部分にアクセスする場合は全表スキャンのほうが効率的です。

関連項目:

8.2 表アクセス・パス

表は、Oracle Databaseにおけるデータ編成の基本単位です。

リレーショナル表は、最も一般的な表タイプです。リレーショナル表には次のような構成上の特徴があります。

  • ヒープ構成表では、行は特定の順序で格納されません。

  • 索引構成表では、主キー値に従って行が整列されます。

  • 外部表は読取り専用の表であり、これらのメタデータはデータベースに格納されますが、データはデータベースの外部に格納されます。

この項には、ヒープ構成表へのオプティマイザのアクセス・パスについて説明する次のトピックがあります。

関連項目:

8.2.1 ヒープ構成表アクセスについて

デフォルトでは、表はヒープとして編成されます。つまり、データベースでは、ユーザーが指定する順序ではなく、最も収まりすいように行が配置されます。

ユーザーが行を追加すると、データベースではデータ・セグメントの最初の使用可能な空き領域に行が格納されます。挿入された順序どおりに行が取得されることは保証されていません。

この項の内容は次のとおりです。

8.2.1.1 データ・ブロックとセグメントでの行の格納: 入門編

データベースでは、行がデータ・ブロックに格納されます。表では、ブロックの下部の任意の行へ書込みが行われます。Oracle Databaseは、ブロック自体を管理するために、行ディレクトリと表ディレクトリが含まれているブロック・オーバーヘッドを使用します。

エクステントは論理的に連続するデータ・ブロックから構成されます。ブロックはディスク上で物理的に連続していない可能性もあります。セグメントは、表領域内の特定の論理記憶域構造のデータがすべて入っている、エクステントの集合です。たとえば、Oracle Databaseでは、1つ以上のエクステントを割り当てることによって、表のデータ・セグメントを形成します。また、データベースは、表の索引セグメントを作成するためにも、1つ以上のエクステントを割り当てます。

デフォルトでは、データベースは永続的なローカル管理表領域に対して、自動セグメント領域管理(ASSM)を使用します。セッションが最初にデータを表に挿入すると、データベースはビットマップ・ブロックをフォーマットします。ビットマップはセグメントのブロックを追跡します。データベースでは、ビットマップを使用して空きブロックが検索され、各ブロックは書き込み前にフォーマットされます。ASSMを使用すると、挿入処理が各ブロックに分散されるため、同時実行性の問題を回避できます。

最高水位標(HWM)は、セグメント内の位置を指し示し、その位置を超えると、データ・ブロックは未フォーマットであり、使用されていません。HWMより下のブロックは、フォーマット済で書き込まれている、フォーマット済で空である、あるいは未フォーマットである場合があります。低い最高水位標(低いHWM)は、この位置より下のすべてのブロックは、データが格納されているか、以前にデータが格納されていたため、フォーマット済であることを示しています。

全表スキャン時は、データベースはフォーマット済であることが既知である低いHWMまでのブロックをすべて読み取り、次に、セグメント・ビットマップを読み取って、HWMと低いHWMの間にあるどのブロックがフォーマット済で安全に読み取れるのかを判別します。データベースは、ブロックが未フォーマットであるため、HWMを超えて読み取ることはありません。

関連項目:

データ・ブロックの記憶域について学習するには、『Oracle Database概要』を参照してください。

8.2.1.2 行アクセスにおけるROWIDの重要性

ヒープ構成表のすべての行にはその表で一意のROWIDがあり、これは行断片の物理アドレスに相当します。ROWIDは行の10バイトの物理アドレスです。

ROWIDは特定のファイル、ブロックおよび行番号を指します。たとえば、ROWID AAAPecAAFAAAABSAAAでは、最後のAAAは行番号を表します。行番号は行ディレクトリ・エントリの索引です。行ディレクトリ・エントリにはブロックにおける行の位置へのポインタが含まれます。

データベースはブロックの下部で行を移動することがあります。たとえば、行の移動が可能な場合、行は、パーティション・キーの更新、フラッシュバック表の操作、表の縮小操作などのために移動できます。データベースがブロック内で移動すると、データベースは行ディレクトリ・エントリを更新してポインタを変更します。ROWIDは一定です。

Oracle Databaseでは、ROWIDを内部的に使用して索引が構築されます。たとえば、Bツリー索引のそれぞれのキーは、対応する行のアドレスを指すROWIDに関連付けられています。物理ROWIDを使用すると、表の1つの行に最速でアクセスでき、データベースではわずか1回のI/Oで行を取得できます。

関連項目:

ROWIDについて学習するには、『Oracle Database概要』を参照してください。

8.2.1.3 ダイレクト・パス読取り

ダイレクト・パス読取りでは、データベースは、SGA全体を回避しながら、ディスクからPGAに直接バッファを読み込みます。

次の図は、SGAにバッファを格納する散布読取りおよび順次読取りと、ダイレクト・パス読取りの違いを示しています。

図8-1 ダイレクト・パス読取り

図8-1の説明が続きます
「図8-1 ダイレクト・パス読取り」の説明

Oracle Databaseがダイレクト・パス読取りを実行する可能性のある状況には、次のようなものがあります。

  • CREATE TABLE AS SELECT文の実行

  • ALTER REBUILD文またはALTER MOVE文の実行

  • 一時表領域からの読取り

  • パラレル問合せ

  • LOBセグメントからの読取り

関連項目:

ダイレクト・パス読取りの待機イベントについて学習するには、『Oracle Databaseパフォーマンス・チューニング・ガイド』を参照してください。

8.2.2 全表スキャン

全表スキャンでは、表のすべての行が読み取られ、選択基準を満たしていない行がフィルタ処理されます。

この項の内容は次のとおりです。

8.2.2.1 オプティマイザが全表スキャンを考慮する場合

一般に、オプティマイザは、別のアクセス・パスを使用できない場合、または使用可能な別のアクセス・パスのコストが高い場合は、全表スキャンを選択します。

次の表では、全表スキャンを選択する一般的な理由を示します。

表8-2 全表スキャンの一般的な理由

理由 説明 さらに学習するには

索引が存在しない。

索引が存在しない場合、オプティマイザは、全表スキャンを使用します。

Oracle Database概要

問合せ述語が、索引付けされた列に関数を適用する。

索引がファンクション索引でない場合、関数が適用された列の値ではなく、その列の値が索引付けされます。典型的なアプリケーション・レベルの誤りは、char_colなどの文字の列を索引付けし、WHERE char_col=1などの構文を使用して列を問い合せることです。データベースでは、暗黙的にTO_NUMBER関数に定数1(索引を使用することはできません)が適用されます。

「unresolvable-reference.html」

SELECT COUNT(*)問合せが発行され、索引が存在するが、索引付けされた列にNULLが含まれている。

索引にはNULLエントリを含めることはできないため、オプティマイザは、索引を使用して、表の行数をカウントすることができません。

Bツリー索引およびNULL

問合せ述語では、最先端のBツリー索引を使用することができない。

たとえば、employees(first_name,last_name)に索引が存在するとします。ユーザーが述語WHERE last_name='KING'を使用して問合せを発行する場合、列first_nameが述語に含まれていないため、オプティマイザは、索引を選択しない可能性があります。ただし、このような場合、オプティマイザが索引スキップ・スキャンの使用を選択することがあります。

索引スキップ・スキャン

問合せは非選択性である。

表のほとんどのブロックを必要とする問合せの場合、オプティマイザは、索引が使用可能であっても、全表スキャンを使用します。全表スキャンではより大きなI/Oコールを使用できます。大きなI/Oコールを少なくすることにより、小さなコールを何度も行うよりもコストが低くなります。

「選択性」

表統計が失効している。

たとえば、以前は小さかった表が大きくなったとします。表統計が失効しており、表の現在のサイズを反映していない場合、オプティマイザは、現在では全表スキャンではなく索引が最も効率的であることを認識できません。

「オプティマイザ統計の概要」

表が小さい。

表に含まれているブロックがn個よりも少なく、最高水位標を下回っている場合(ここで、nは、DB_FILE_MULTIBLOCK_READ_COUNT初期化パラメータの設定と同じです)、全表スキャンのほうが索引レンジ・スキャンよりもコストが低いことがあります。このスキャンは、アクセス先の表の部分や索引の存在に関係なく、よりコストが低くなる可能性があります。

Oracle Databaseリファレンス

表の並列度が高い。

表の並列度が高いと、レンジ・スキャンよりも全表スキャンの方向にオプティマイザを偏らせます。並列度を判断するには、ALL_TABLES.DEGREE列の値を問い合せます。

Oracle Databaseリファレンス

問合せで全表スキャンのヒントが使用される。

ヒントFULL(table alias)は、オプティマイザに全表スキャンの使用を指示するものです。

Oracle Database SQL言語リファレンス

8.2.2.2 全表スキャンの仕組み

全表スキャンでは、データベースは、最高水位標を下回るすべてのフォーマット済ブロックを順に読み取ります。データベースは、各ブロックを1回のみ読み取ります。

次の図は、表セグメントのスキャンで、最高水位標を下回る未フォーマットのブロックがどのようにスキップされるかを示しています。

ブロックは隣接しているため、データベースでは、I/Oコールを単一ブロックより大きくすることでスキャンを高速化できます。これは、マルチブロック読取りと呼ばれています。読取りコールのサイズは、1ブロックからDB_FILE_MULTIBLOCK_READ_COUNT初期化パラメータで指定されているブロック数までの範囲になります。たとえば、このパラメータを4に設定すると、データベースは単一コールで最大4ブロックを読み取ります。

全表スキャン中の、ブロックのキャッシングのアルゴリズムは複雑です。たとえば、表が小さいか大きいかによって、ブロックがキャッシュされる方法は異なります。

関連項目:

8.2.2.3 全表スキャン: 例

この例では、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)

8.2.3 ROWIDによる表アクセス

ROWIDは、データの格納場所の内部表現です。

行のROWIDは、行が含まれるデータファイルとデータ・ブロック、およびブロック内での行の位置を指定します。ROWIDの指定による行の位置特定は、単一行を取得する最も高速な方法です。これは、取得する行のデータベース内での正確な位置が指定されるためです。

ノート:

ROWIDは、バージョン間で変わってしまう場合があります。行は移動する可能性があるため、位置に基づくアクセスはお薦めしません。

この項の内容は次のとおりです。

関連項目:

ROWIDについてさらに学習するには、『Oracle Database開発ガイド』を参照してください。

8.2.3.1 オプティマイザがROWIDによる表アクセスを選択する場合

ほとんどの場合、データベースは1つ以上の索引のスキャン後にROWIDによって表にアクセスします。

しかし、ROWIDによる表アクセスでは、すべての索引スキャンに従う必要はありません。必要な列がすべて索引に含まれていると、ROWIDによるアクセスが発生しない可能性があります。

関連トピック

8.2.3.2 ROWIDによる表アクセスの仕組み

ROWIDによって表にアクセスするには、データベースで複数のステップを実行します。

データベースでは、次のことが実行されます。

  1. 文のWHERE句または1つ以上の索引の索引スキャンのいずれかを使用して、選択した行のROWIDを取得します。

    索引内に存在しない文の中の列には、表アクセスが必要になる場合があります。

  2. ROWIDに基づいて、表内で選択された各行の位置を特定します。

8.2.3.3 ROWIDによる表アクセス: 例

この例では、hr.employees表の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)

8.2.4 サンプル表スキャン

サンプル表スキャンでは、単純な表、または結合およびビューを含む文などの複合SELECT文からデータのランダムなサンプルが取り出されます。

この項の内容は次のとおりです。

8.2.4.1 オプティマイザがサンプル表スキャンを選択する場合

文のFROM句にSAMPLEキーワードが含まれている場合、データベースでは、サンプル表スキャンが使用されます。

SAMPLE句の形式は、次のとおりです。

  • SAMPLE (sample_percent)

    データベースは、表内の行を指定の割合だけ読み取って、サンプル表スキャンを実行します。

  • SAMPLE BLOCK (sample_percent)

    データベースは、表ブロックを指定の割合だけ読み取って、サンプル表スキャンを実行します。

sample_percentには、行またはブロックの合計数の何パーセントがサンプルに含まれるかを指定します。.000001以上100未満の範囲の値を指定します。この割合は、各行(ブロック・サンプリングでは行の各クラスタ)が、サンプル用に選択される可能性を示します。データベースがsample_percent分の行を厳密に取り出すということではありません。

ノート:

ブロック・サンプリングは、全表スキャン中または高速全索引スキャン中にのみ使用可能です。より効率的な実行パスが存在する場合、ブロック・サンプリングは実行されません。特定の表または索引に対するブロック・サンプリングを確実に実行する場合は、FULLまたはINDEX_FFSのヒントを使用します。

関連項目:

8.2.4.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)|
-------------------------------------------------------------------------

8.2.5 インメモリー表スキャン

インメモリー・スキャンでは、インメモリー列ストア(IM列ストア)から行が取得されます。

IM列ストアは任意に入力できるSGA領域です。ここには、迅速にスキャンできるように最適化された特殊な列形式で、表のコピーおよびパーティションが格納されています。

この項の内容は次のとおりです。

関連項目:

IM列ストアの概要は、『Oracle Database In-Memoryガイド』を参照してください。

8.2.5.1 オプティマイザがインメモリー表スキャンを選択する場合

オプティマイザのコスト・モデルは、IM列ストアの内容を認識します。

ユーザーがIM列ストア内の表を参照する問合せを実行すると、オプティマイザによって、インメモリー表スキャンなどのすべての考えられるアクセス方法のコストが計算され、コストが最小のアクセス方法が選択されます。

8.2.5.2 インメモリー問合せの制御

初期化パラメータを使用してインメモリー問合せを制御できます。

次のデータベース初期化パラメータは、インメモリー機能に影響します。

  • INMEMORY_QUERY

    このパラメータでは、セッションまたはシステム・レベルでデータベースのインメモリー問合せを有効または無効にします。このパラメータは、IM列ストアを使用する場合と使用しない場合でワークロードをテストする際に役立ちます。

  • OPTIMIZER_INMEMORY_AWARE

    このパラメータでは、オプティマイザ・コスト・モデル、表拡張、ブルーム・フィルタなどに対して行われたすべてのインメモリー拡張を有効(TRUE)または無効(FALSE)にします。パラメータをFALSEに設定すると、オプティマイザはSQL文の最適化中に表のインメモリー・プロパティを無視します。

  • OPTIMIZER_FEATURES_ENABLE

    12.1.0.2より小さい値に設定した場合、このパラメータの効果はOPTIMIZER_INMEMORY_AWAREFALSEに設定した場合と同じです。

インメモリー問合せを有効または無効にするには、INMEMORYまたはNO_INMEMORYヒントを指定できます。これは、問合せごとのINMEMORY_QUERY初期化パラメータと同じです。SQL文でINMEMORYヒントを使用し、それが参照するオブジェクトがIM列ストアにまだロードされていない場合、データベースは文を実行する前にIM列ストアにオブジェクトが移入されるのを待機しません。ただし、オブジェクトの初期アクセスにより、IM列ストア内のオブジェクトの移入がトリガーされます。

関連項目:

8.2.5.3 インメモリー表スキャン: 例

この例では、TABLE ACCESS INMEMORY操作を含む実行計画を示します。

次の例に、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)

8.3 Bツリー索引アクセス・パス

索引は、表または表クラスタに関連するオプションの構造であり、索引によってデータ・アクセスを高速化できる場合があります。

表の1つ以上の列に索引を作成することによって、場合によって、ランダムに分散している行の小さなセットを表から取得できるようになります。索引は、ディスクI/Oを削減するための様々な手段のうちの1つです。

この項の内容は次のとおりです。

関連項目:

8.3.1 Bツリー索引アクセスについて

Bツリー(バランス・ツリーの略)は、最も一般的なタイプのデータベース索引です。

Bツリー索引は、複数の範囲に分割された順序付きの値リストです。Bツリーは、キーを行または行の範囲と関連付けることによって、完全一致や範囲検索など、広範囲の問合せに対して優れた検索パフォーマンスを提供します。

この項の内容は次のとおりです。

8.3.1.1 Bツリー索引構造

Bツリー索引には、検索用のブランチ・ブロックと、値を格納するリーフ・ブロックの2種類のブロックがあります。

次の図は、Bツリー索引の論理構造を示しています。ブランチ・ブロックには、2つのキーの分岐を決定する際に必要な、最小のキー接頭辞が格納されます。リーフ・ブロックには、すべての索引付きデータ値と、実際の行を検索するための対応するROWIDが含まれています。各索引エントリは(キー、ROWID)によってソートされます。リーフ・ブロックは二重にリンクされます。

図8-3 Bツリー索引構造

図8-3の説明が続きます
「図8-3 Bツリー索引構造」の説明
8.3.1.2 索引記憶が索引スキャンに与える影響

ビットマップ索引ブロックは、索引セグメントのどこにでも出現する可能性があります。

図8-3 に、リーフ・ブロックを隣りあわせて示しています。たとえば、1から10ブロックは、11から19ブロックの隣で前にあります。この順序は、索引エントリを接続するリンクされたリストを示しています。ただし、索引ブロックは、索引セグメント内で順番に格納される必要はありません。たとえば、246から250ブロックは、1から10ブロックの直前を含め、セグメント内のどこにでも出現することができます。このため、順序付き索引スキャンでは単一ブロックI/Oを実行する必要があります。データベースは、索引ブロックを読み取ることでどの索引ブロックを次に読み取る必要があるかを判断する必要があります。

索引ブロック本体は、表の行と同様に、索引エントリをヒープに格納します。たとえば、値10がまず表に挿入されると、キー10を含む索引エントリが索引ブロックの下部に挿入されます。次に、0が表に挿入されると、キー0の索引エントリが10のエントリの上部に挿入されます。したがって、ブロック本体の索引エントリは、キーの順には格納されません。ただし、索引ブロック内では、行ヘッダーはレコードをキーの順に格納します。たとえば、ヘッダーの最初のレコードはキー0を含む索引エントリを指し、以下同様に、キー10の索引エントリを指すレコードまで順に続きます。したがって、索引スキャンは行ヘッダーを読み取ることで、レンジ・スキャンをどこで開始し終了するかを判断できます。それにより、ブロックのすべてのエントリを読み取る必要性が回避されます。

関連項目:

索引ブロックについて学習するには、『Oracle Database概要』を参照してください。

8.3.1.3 一意索引と非一意索引

非一意索引では、データベースはROWIDを格納するために、追加列としてROWIDをキーに付加したものを格納します。エントリは長さバイトを追加してキーを一意にしています。

たとえば、図8-3に示されている非一意索引の最初の索引キーは0,rowidの組合せであり、単に0ではありません。データは索引キーの値でソートされてから、ROWIDの昇順でソートされます。たとえば、エントリは次のように並べ替えられます。

0,AAAPvCAAFAAAAFaAAa
0,AAAPvCAAFAAAAFaAAg
0,AAAPvCAAFAAAAFaAAl
2,AAAPvCAAFAAAAFaAAm

一意の索引では、索引キーにROWIDは含まれません。データは、012といった索引キーの値のみでソートされます。

関連項目:

一意および非一意の索引の概要は、『Oracle Database概要』を参照してください。

8.3.1.4 Bツリー索引およびNULL

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 |
---------------------------------------------------------------------------

次の例は、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   |
---------------------------------------------------------------------------
| 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):
   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): 
   1 - filter("DEPARTMENT_ID" IS NOT NULL)

8.3.2 索引一意スキャン

索引一意スキャンはROWID 1までを返します。

この項の内容は次のとおりです。

8.3.2.1 オプティマイザが索引一意スキャンを考慮する場合

索引一意スキャンには等価述語が必要です。

具体的には、問合せ述語が等価演算子を使用して一意索引キーのすべての列を参照する場合(WHERE prod_id=10など)のみ、データベースは一意スキャンを実行します。

列に対して一意でない索引がすでに存在している可能性があるため、一意キー制約または主キー制約は、それだけでは索引一意スキャンを生成するのに不十分です。次の例を検討してください。ここでは、t_table表を作成した後でnumcolに一意でない索引を作成しています。

SQL> CREATE TABLE t_table(numcol INT);
SQL> CREATE INDEX t_table_idx ON t_table(numcol);
SQL> SELECT UNIQUENESS FROM USER_INDEXES WHERE INDEX_NAME = 'T_TABLE_IDX';

UNIQUENES
---------
NONUNIQUE

次のコードは、一意でない索引が付いた列に主キー制約を作成します。この結果は、索引一意スキャンではなく索引レンジ・スキャンになります。

SQL> ALTER TABLE t_table ADD CONSTRAINT t_table_pk PRIMARY KEY(numcol);
SQL> SET AUTOTRACE TRACEONLY EXPLAIN
SQL> 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)ヒントを使用して使用する索引を指定できますが、特定の索引アクセス・パスは指定できません。

関連項目:

8.3.2.2 索引一意スキャンの仕組み

このスキャンでは、指定したキーの索引を順番に検索します。索引の一意スキャンでは、2つ目のレコードがないことがわかっているため、最初のレコードが見つかるとただちに処理が停止します。データベースは索引エントリからROWIDを取得し、ROWIDで指定される行を取得します。

次の図は、索引一意スキャンを示しています。文は、主キー索引があるprod_id列の製品IDが19のレコードをリクエストします。

図8-4 索引一意スキャン

図8-4の説明が続きます
「図8-4 索引一意スキャン」の説明
8.3.2.3 索引一意スキャン: 例

この例では、一意スキャンを使用して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)

8.3.3 索引レンジ・スキャン

索引レンジ・スキャンは、値の順序付きスキャンです。

スキャンの範囲は、両側での境界スキャン、あるいは片側または両側での非有界スキャンとすることができます。オプティマイザは、選択性が高い問合せには、通常、レンジ・スキャンを選択します。

デフォルトでは、データベースは索引を昇順に格納し、それらのスキャンも同じ順序で行います。たとえば、述語department_id >= 20を含む問合せにはレンジ・スキャンが使用され、203040といった索引キーでソートされた行が返されます。複数の索引エントリに同一のキーがある場合は、データベースはそれらをROWIDの昇順で返します。すなわち、0,AAAPvCAAFAAAAFaAAaの次に0,AAAPvCAAFAAAAFaAAgなどとなります。

索引レンジ・スキャン降順は、行が降順で戻されること以外、索引レンジ・スキャンと同じです。通常、降順スキャンが使用されるのは、データを降順に並べる場合か、指定された値より小さい値を検索する場合です。

この項の内容は次のとおりです。

8.3.3.1 オプティマイザが索引レンジ・スキャンを考慮する場合

索引レンジ・スキャンの場合、複数の値が索引キーに使用できる必要があります。

特に、オプティマイザは、次の状況で索引レンジ・スキャンを検討します。

  • 索引の1つ以上の先頭列を条件に指定します。

    条件は、1つ以上の式および論理(ブール)演算子の組合せを指定し、TRUEFALSEまたは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言語リファレンス』を参照してください

8.3.3.2 索引レンジ・スキャンの仕組み

索引レンジ・スキャン中、Oracle Databaseはルートからブランチに進みます。

一般に、スキャン・アルゴリズムは次のとおりです。

  1. ルート・ブロックを読み取ります。

  2. ブランチ・ブロックを読み取ります。

  3. すべてのデータが取り出されるまで、次のステップを交互に行います。

    1. リーフ・ブロックを読み取って、ROWIDを取得します。

    2. 表ブロックを読み取って、行を取得します。

    ノート:

    場合によっては、索引スキャンは索引ブロックのセットを読み取り、ROWIDをソートしてから、表ブロックのセットを読み取ります。

そのため、索引をスキャンするには、データベースではリーフ・ブロックを後方または前方に移動します。たとえば、20から40のIDを求めるスキャンでは、最小キー値20以上を含む最初のリーフ・ブロックが検索されます。40より大きい値が見つかるまで、スキャンはリンクされたリーフ・ノード・リストを水平に進み、その後停止します。

次の図は、昇順を使用した索引レンジ・スキャンを示しています。文は、department_id列(非一意索引を含む)の値が20employeesレコードをリクエストします。この例では、部門20には2つの索引エントリが存在します。

例8-5 索引レンジ・スキャン

図8-5の説明が続きます
「図8-5 索引レンジ・スキャン」の説明
8.3.3.3 索引レンジ・スキャン: 例

この例では、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)
8.3.3.4 索引レンジ・スキャン降順: 例

この例では、索引を使用して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で指定される行を取得します。

8.3.4 全索引スキャン

全索引スキャンは、索引全体を順に読み取ります。索引のデータは索引キー順に並んでいるため、全索引スキャンで個別のソート操作を排除できます。

この項の内容は次のとおりです。

8.3.4.1 オプティマイザが全索引スキャンを考慮する場合

オプティマイザは、様々な状況で全索引スキャンを検討します。

状況は次のとおりです。

  • 述語が索引の列を参照している。この列は先頭列である必要はありません。

  • 述語は指定されていないが、次のすべての条件は満たしている。

    • 表および問合せのすべての列は索引にある。

    • 少なくとも1つの索引付き列がNULLではない。

  • 問合せに、索引付きのNULL不可能列に対するORDER BYが含まれる。

8.3.4.2 全索引スキャンの仕組み

データベースはルート・ブロックを読み取り、リーフ・ブロックに到達するまで索引の左方向(または、降順全スキャンを行っている場合は右)に移動します。

その後、データベースはリーフ・ブロックに到達し、スキャンは一度に1ブロックずつ、ソートされた順序で下位の索引を読み取ります。データベースは、マルチブロックI/Oではなく単一ブロックI/Oを使用します。

次の図は、全索引スキャンを示しています。文は、department_idで並べられているdepartmentsレコードをリクエストします。

図8-6 全索引スキャン

図8-6の説明が続きます
「図8-6 全索引スキャン」の説明
8.3.4.3 全索引スキャン: 例

この例では、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でソートされるため、データベースは取得した行の個別のソート操作を回避します。

8.3.5 高速全索引スキャン

高速全索引スキャンは、ソートされていない索引ブロックをディスク上に存在しているとおりに読み取ります。このスキャンは表を調べるために索引を使用しませんが、基本的には索引自体を表のように使用しながら、表ではなく索引を読み取ります。

この項の内容は次のとおりです。

8.3.5.1 オプティマイザが高速全索引スキャンを考慮する場合

問合せが索引の属性のみにアクセスする場合、オプティマイザはこのスキャンを考慮します。

ノート:

全スキャンとは異なり、高速全スキャンではソート操作を排除できません。これは索引を順番に読み取らないためです。

INDEX_FFS(table_name index_name)ヒントにより高速全索引スキャンが強制されます。

関連項目:

INDEXヒントについてさらに学習するには、Oracle Database SQL言語リファレンスを参照してください

8.3.5.2 高速全索引スキャンの仕組み

データベースは、マルチブロックI/Oを使用して、ルート・ブロックと、すべてのリーフ・ブロックおよびブランチ・ブロックを読み取ります。データベースは、ブランチ・ブロックとルート・ブロックを無視して、リーフ・ブロックの索引エントリを読み取ります。

8.3.5.3 高速全索引スキャン: 例

この例では、オプティマイザ・ヒントの結果として高速全索引スキャンを使用します。

次の文は、部門の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 |
--------------------------------------------------------------------------

8.3.6 索引スキップ・スキャン

索引スキップ・スキャンは、コンポジット索引の初期列が問合せでスキップされる(指定されない)場合に行われます。

この項の内容は次のとおりです。

関連項目:

Oracle Database概要

8.3.6.1 オプティマイザが索引スキップ・スキャンを考慮する場合

多くの場合、索引ブロックをスキャンするほうが、表データ・ブロックをスキャンしたり全索引スキャンを実行するより高速です。

次の基準が当てはまる場合、オプティマイザはスキップ・スキャンを考慮します。

  • コンポジット索引の先頭列が問合せ述語で指定されていない。

    たとえば、問合せ述語がcust_gender列を参照せず、コンポジット索引キーが(cust_gender,cust_email)の場合です。

  • コンポジット索引の先頭列に個別値がほとんどなく、索引の非先頭キーに個別値が多数存在する。

    たとえば、コンポジット索引キーが(cust_gender,cust_email)である場合、cust_gender列には個別値が2つしか存在しませんが、cust_emailには数千存在します。

8.3.6.2 索引スキップ・スキャンの仕組み

索引スキップ・スキャンは、コンポジット索引をさらに小さい副索引に論理的に分割します。

論理副索引の個数は、索引の先頭列にある個別値の数によって判断されます。個別値の数が少ないほど、オプティマイザが作成しなければならない論理副索引の数が少なくなり、スキャンはより効率的になります。スキャンは各論理索引を個別に読み取り、非先頭列に対するフィルタ条件に一致しない索引ブロックをスキップします。

8.3.6.3 索引スキップ・スキャン: 例

この例では、sh.customers表の問合せを満たすために索引スキップ・スキャンを使用します。

customers表にはcust_genderという列があり、この列の値はMまたはFです。データベースにユーザーshとしてログインしているときに、次のように列(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_genderWHERE句で指定されていませんが、データベースでは、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.example.comの顧客のレコードを検索する場合、最初に先頭値Fを持つ副索引が検索され、次に先頭値Mを持つ副索引が検索されます。その結果、データベースでは問合せが次のように処理されます。

( SELECT * 
  FROM   sh.customers 
  WHERE  cust_gender = 'F' 
  AND    cust_email = 'Abbey@company.example.com' )
UNION ALL
( SELECT * 
  FROM   sh.customers 
  WHERE  cust_gender = 'M'
  AND    cust_email = 'Abbey@company.example.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概要』を参照してください。

8.3.7 索引結合スキャン

索引結合スキャンは、問合せによってリクエストされたすべての列をまとめて返す、複数の索引のハッシュ結合です。すべてのデータは索引から取得されるため、データベースは表にアクセスする必要がありません。

この項の内容は次のとおりです。

8.3.7.1 オプティマイザが索引結合スキャンを考慮する場合

表アクセスの回避が最もコスト効率の高い選択肢の場合があります。

オプティマイザは、次の状況で索引結合を考慮します。

  • 表アクセスの必要なしに、問合せでリクエストされたすべてのデータを複数索引のハッシュ結合によって取得する。

  • 表から行を取得するコストは、表から行を取得せずに索引を読み込む場合よりも高い。索引結合はコストが高くなることがよくあります。たとえば、2つの索引をスキャンして結合する場合、最も選択的な索引を選択し、表を調べるほうがコストがかかりません。

INDEX_JOIN(table_name)ヒントを使用して索引結合を指定できます。

8.3.7.2 索引結合スキャンの仕組み

索引結合では複数の索引がスキャンされ、そのスキャンから取得したROWIDでのハッシュ結合を使用して行を返します。

索引結合スキャンでは、表アクセスは常に回避されます。たとえば、1つの表の2つの索引を結合する処理は次のとおりです。

  1. 最初の索引をスキャンしてROWIDを取得します。

  2. 2番目の索引をスキャンしてROWIDを取得します。

  3. ROWIDでのハッシュ結合を実行し、行を取得します。

8.3.7.3 索引結合スキャン: 例

この例では、索引結合を指定して、姓が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

次の例では、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%')

8.4 ビットマップ索引アクセス・パス

ビットマップ索引では、索引付けられたデータをROWID範囲と組み合せます。

この項では、ビットマップ索引、およびより頻繁に使われるビットマップ索引アクセス・パスについて説明します。

8.4.1 ビットマップ索引アクセスについて

従来型のBツリー索引では、1つの索引エントリは単一の行を指します。ビットマップ索引では、キーは索引が付けられたデータとROWID範囲の組合せです。

データベースでは、索引キーごとに1つ以上のビットマップが格納されます。1および0 が連続するビットマップの各値は、ROWID範囲内の行を指します。したがって、ビットマップ索引では、1つの索引エントリが単一行ではなく行のセットを指します。

この項の内容は次のとおりです。

8.4.1.1 ビットマップ索引とBツリー索引の違い

ビットマップ索引はBツリー索引とは異なるキーを使用しますが、Bツリー構造に格納されます。

次の表は、索引エントリの種類における違いを示します。

表8-3 Bツリーおよびビットマップの索引エントリ

索引エントリ キー データ

一意のBツリー

索引付けられたデータのみ

ROWID

employees.employee_id列の索引エントリでは、社員ID 101がキーで、ROWID AAAPvCAAFAAAAFaAAaがデータです。

101,AAAPvCAAFAAAAFaAAa

一意ではないBツリー

ROWIDと組み合せた索引付けられたデータ

なし

employees.last_name列の索引エントリでは、名前とROWIDの組合せSmith,AAAPvCAAFAAAAFaAAaがキーで、データはありません。

Smith,AAAPvCAAFAAAAFaAAa

ビットマップ

ROWID範囲と組み合せた索引付けられたデータ

ビットマップ

customers.cust_gender列の索引エントリでは、M,low-rowid,high-rowidの部分がキーで、1および0の連続値がデータです。

M,low-rowid,high-rowid,1000101010101010

データベースでは、ビットマップ索引をBツリー構造に格納します。データベースでは、索引が定義される属性のセットであるキーの最初の部分のBツリーが即座に検索され、対応するROWID範囲とビットマップが取得されます。

関連項目:

8.4.1.2 ビットマップ索引の目的

ビットマップ索引は一般に、個別値の数(NDV)が少ない、または中程度の頻繁に変更されないデータに適しています。

一般に、Bツリー索引は、NDVが多く、DMLアクティビティが頻繁に発生する列に適しています。たとえば、オプティマイザは、少数の行を返すsales.amount列の問合せについてBツリー索引を選択する可能性があります。これに対して、customers.state列とcustomers.county列は、個別値が少なく、頻繁に更新されないため、効率的なAND演算およびOR演算の利点を得られることから、ビットマップ索引の候補となります。

ビットマップ索引は、データ・ウェアハウス内の非定型問合せを高速化するために有用な方法です。これらは、スター型変換の基盤となります。特に、ビットマップ索引は次を含む問合せで効果的です。

  • WHERE句に複数の条件がある

    すべての条件ではなく一部の条件のみを満たす行は、表自体がアクセスされる前にデータベースによって除外されます。

  • NDVが少ない、または中程度の列に対するANDORおよびNOT演算

    ビットマップ索引を組み合せると、これらの演算がより効率的になります。データベースでは、ビットマップ索引から即座にビットマップをマージできます。たとえば、customers.state列とcustomers.county列にビットマップ索引が存在する場合、これらの索引によって次の問合せのパフォーマンスが大幅に向上します。

    SELECT * 
    FROM   customers 
    WHERE  state = 'CA' 
    AND    county = 'San Mateo'

    データベースでは、マージされたビットマップ内の1の値を効率的にROWIDに変換できます。

  • COUNT関数

    データベースでは、表をスキャンすることなくビットマップ索引をスキャンできます。

  • NULL値を選択する述語

    Bツリー索引と異なり、ビットマップ索引にNULLを含めることができます。列のnull数をカウントする問合せでは、表をスキャンせずにビットマップ索引を使用できます。

  • DMLが頻繁でない列

    理由は、1つの索引キーが多数の行を指しているためです。索引付けられたデータがセッションで変更される場合、データベースはビットマップ内の単一ビットをロックできず、索引エントリ全体をロックします。これにより、実際にはビットマップによりポイントされた行がロックされます。たとえば、特定の顧客の居住郡がSan MateoからAlamedaに変更された場合、データベースは、ビットマップ内のSan Mateo索引エントリおよびAlameda索引エントリへの排他的アクセスを取得する必要があります。これらの2つの値を含む行は、COMMITまで変更できません。

関連項目:

8.4.1.3 ビットマップおよびROWID

ビットマップの各値は、行の値がビットマップ条件に一致する場合は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)

AAA ...

CCC ...

0

0

0

0

0

1

married

AAA ...

CCC ...

1

0

1

1

1

0

single

AAA ...

CCC ...

0

1

0

0

0

0

single

DDD ...

EEE ...

1

0

1

0

1

1

表8-4のように、Bツリー索引とは異なり、ビットマップ索引は、完全にNULL値からなるキーを含む場合があります。表8-4では、NULLにおいて範囲内の6番目の行の値が1です。これは、範囲内の6番目の行は、cust_marital_status値がNULLであることを意味します。NULLの索引作成は、集計関数COUNTによる問合せなど、一部のSQL文に使用できます。

関連項目:

ROWID形式について学習するには、『Oracle Database概要』

8.4.1.4 ビットマップ結合索引

ビットマップ結合索引は、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)。これにより、customerssales表の結合はなくなります。

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概要を参照してください

8.4.1.5 ビットマップ・ストレージ

ビットマップ索引は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値を圧縮できます。

8.4.2 ビットマップのROWIDへの変換

ビットマップ変換は、ビットマップのエントリと表の行の間で変換されます。この変換は、エントリから行(TO ROWID)、または行からエントリ(FROM ROWID)へと変換できます。

この項の内容は次のとおりです。

8.4.2.1 オプティマイザがビットマップのROWIDへの変換を選択する場合

オプティマイザでは、ビットマップ索引エントリを使用する表の行を取得時に必ず変換が使用されます。

8.4.2.2 ビットマップのROWIDへの変換の仕組み

概念上、ビットマップは表として表せます。

たとえば、表8-4は、customers行番号が列、cust_marital_status値が行の表として、ビットマップを表しています。表8-4の各フィールドには1または0の値があり、行の列値を表します。概念的には、ビットマップ変換では、「ビットマップのFフィールドが表のM番目のブロックのN番目の行に対応する」または「表のM番目のブロックのN番目の行がビットマップのFフィールドに対応する」という内部アルゴリズムが使用されます。

8.4.2.3 ビットマップの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)

8.4.3 ビットマップ索引単一値

この種類のアクセス・パスは、ビットマップ索引を使用して単一のキー値を検索します。

この項の内容は次のとおりです。

8.4.3.1 オプティマイザがビットマップ索引単一値を考慮する場合

述語に等価演算子が含まれる場合に、オプティマイザはこの種類のアクセス・パスを考慮します。

8.4.3.2 ビットマップ索引単一値の仕組み

問合せは単一ビットマップをスキャンして1の値を含む位置を調べます。データベースは1の値をROWIDに変換し、そのROWIDを使用して行を検索します。

データベースは単一のビットマップを処理することのみが必要です。たとえば、次の表はsh.customers.cust_marital_status列内の値widowedに対するビットマップ索引(2つのビットマップ・ピース)を表しています。ステータスがwidowedの顧客の問合せに対応するため、データベースはwidowedビットマップ内の1の値をそれぞれ検索し、対応する行のROWIDを特定します。

表8-5 ビットマップ索引エントリ

列値 範囲の開始ROWID 範囲の終了ROWID 範囲の最初の行 範囲の2番目の行 範囲の3番目の行 範囲の4番目の行 範囲の5番目の行 範囲の6番目の行

widowed

AAA ...

CCC ...

0

1

0

0

0

0

widowed

DDD ...

EEE ...

1

0

1

0

1

1

8.4.3.3 ビットマップ索引単一値: 例

この例では、オプティマイザは等価述語を使用した問合せを満たすためにビットマップ索引の単一値演算を選択します。

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')

8.4.4 ビットマップ索引レンジ・スキャン

この種類のアクセス・パスは、ビットマップ索引を使用して値の範囲を検索します。

この項の内容は次のとおりです。

8.4.4.1 オプティマイザがビットマップ索引レンジ・スキャンを考慮する場合

述語で値の範囲が選択される場合に、オプティマイザはこの種類のアクセス・パスを検討します。

スキャンの範囲は、両側での境界スキャン、あるいは片側または両側での非有界スキャンとすることができます。オプティマイザは、通常、選択問合せにレンジ・スキャンを選択します。

8.4.4.2 ビットマップ索引レンジ・スキャンの仕組み

このスキャンは、Bツリーのレンジ・スキャンと同じように機能します。

たとえば、次の表はsh.customers.cust_year_of_birth列に対するビットマップ索引の3つの値を表しています。問合せで1917年より前に生まれたすべての顧客をリクエストしている場合、データベースはこの索引で1917より小さい値をスキャンして、1を持つ行のROWIDを取得します。

表8-6 ビットマップ索引エントリ

列値 範囲の開始ROWID 範囲の終了ROWID 範囲の最初の行 範囲の2番目の行 範囲の3番目の行 範囲の4番目の行 範囲の5番目の行 範囲の6番目の行

1913

AAA ...

CCC ...

0

0

0

0

0

1

1917

AAA ...

CCC ...

1

0

1

1

1

0

1918

AAA ...

CCC ...

0

1

0

0

0

0

1918

DDD ...

EEE ...

1

0

1

0

1

1

8.4.4.3 ビットマップ索引レンジ・スキャン: 例

この例では、レンジ・スキャンを使用して単一の年より前に生まれた顧客を選択します。

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)

8.4.5 ビットマップ・マージ

このアクセス・パスは複数のビットマップを結合し、単一のビットマップを結果として返します。

ビットマップ・マージが、実行計画のBITMAP MERGE操作により示されています。

この項の内容は次のとおりです。

8.4.5.1 オプティマイザがビットマップ・マージを考慮する場合

通常、オプティマイザはビットマップ・マージを使用して、ビットマップ索引レンジ・スキャンにより生成されたビットマップを結合します。

8.4.5.2 ビットマップ・マージの仕組み

結合では、2つのビットマップ間でブールOR演算が使用されます。結果のビットマップでは、最初のビットマップのすべての行が選択され、さらに後続のすべてのビットマップのすべての行も選択されます。

問合せが1918年より前に生まれたすべての顧客を選択する場合があります。次の例では、3つのcustomers.cust_year_of_birthキー19171916、および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の値が、19151916または1917の値を含む行に対応します。

8.4.5.3 ビットマップ・マージ: 例

この例は、範囲述語を使用した問合せを最適化するためにデータベースがビットマップをマージする方法を示しています。

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)。データベースは、Fcust_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)

8.5 表クラスタ・アクセス・パス

表クラスタとは、共通の列を共有し、関連するデータを同じブロックに格納する表のグループのことです。表がクラスタ化されると、1つのデータ・ブロックに複数の表の行を含めることができます。

この項の内容は次のとおりです。

関連項目:

表クラスタの概要は、『Oracle Database概要』を参照してください。

8.5.1 クラスタ・スキャン

索引クラスタとは、索引を使用してデータを検索する表クラスタのことです。

クラスタ索引とは、クラスタ・キーのBツリー索引のことです。クラスタ・スキャンでは、索引クラスタに格納された表から、同じクラスタ・キー値を持つすべての行が取得されます。

この項の内容は次のとおりです。

8.5.1.1 オプティマイザがクラスタ・スキャンを考慮する場合

索引付きクラスタの表に問合せがアクセスする場合に、クラスタ・スキャンが考慮されます。

8.5.1.2 クラスタ・スキャンの仕組み

索引クラスタでは、同じクラスタ・キー値を持つすべての行が同じデータ・ブロックに格納されます。

たとえば、hr.employees2表およびhr.departments2表がemp_dept_clusterでクラスタ化され、クラスタ・キーがdepartment_idの場合、データベースに格納されるのは、同じブロックの部門10のすべての従業員、同じブロックの部門20のすべての従業員などになります。

Bツリー・クラスタ索引は、データを含むブロックのデータベース・ブロック・アドレス(DBA)にクラスタ・キー値を関連付けます。たとえば、キー30の索引エントリには、部門30の従業員の行を含むブロックのアドレスが表示されます。

30,AADAAAA9d

ユーザーがクラスタの行を要求すると、データベースは索引をスキャンしてその行を含むブロックのDBAを取得します。次に、このDBAに基づいて行の位置を特定します。

8.5.1.3 クラスタ・スキャン: 例

この例では、employeesおよびdepartments表をdepartment_id列でクラスタ化し、このクラスタで単一の部門を問い合せます。

ユーザー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概要』を参照してください。

8.5.2 ハッシュ・スキャン

ハッシュ・クラスタは、索引キーがハッシュ関数に置き換わることを除いて、索引付きクラスタと似ています。別個のクラスタ索引は存在しません。

ハッシュ・クラスタでは、データが索引になりますハッシュ・スキャンでは、ハッシュ値に基づいてハッシュ・クラスタ内の行の位置を特定します。

この項の内容は次のとおりです。

8.5.2.1 オプティマイザがハッシュ・スキャンを考慮する場合

ハッシュ・クラスタの表に問合せがアクセスする場合に、ハッシュ・スキャンが考慮されます。

8.5.2.2 ハッシュ・スキャンの仕組み

ハッシュ・クラスタ内においては、同一のハッシュ値を持つすべての行が同じデータ・ブロックに格納されています。

クラスタのハッシュ・スキャンを実行するには、最初に、文で指定されたクラスタ・キー値にハッシュ関数を適用することによって、ハッシュ値を取得します。次に、Oracle Databaseはこのハッシュ値の行を含むデータ・ブロックをスキャンします。

8.5.2.3 ハッシュ・スキャン: 例

この例では、employeesおよびdepartments表をdepartment_id列でハッシュ化し、このクラスタで単一の部門を問い合せます。

ハッシュ・クラスタおよびクラスタの表を次のように作成します。

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概要』を参照してください。