ヘッダーをスキップ
Oracle® Database SQLチューニング・ガイド
12cリリース1(12.1)
B71277-02
  ドキュメント・ライブラリへ移動
ライブラリ
製品リストへ移動
製品
目次へ移動
目次
索引へ移動
索引

前
 
次
 

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

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

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

アクセス・パスとは、問合せが行ソースから行を取り出す方法のことです。行ソースは、実行計画のステップによって戻される行のセットです。表、ビュー、または結合処理あるいはグループ化処理の結果が行ソースになる可能性があります。

データベースでは、リレーショナル・データ構造ごとに異なるアクセス・パスを使用します(これらの構造の概要は、『Oracle Database概要』を参照してください)。表6-1は、主なデータ構造に対する一般的なアクセス・パスについてまとめたものです。

表6-1 データ構造とアクセス・パス

アクセス・パス ヒープ構成表 Bツリー索引およびIOT ビットマップ索引 表クラスタ

全表スキャン


×




ROWIDによる表アクセス


×




サンプル表スキャン


×




索引一意スキャン



×



索引レンジ・スキャン



×



全索引スキャン



×



高速全索引スキャン



×



索引スキップ・スキャン



×



索引結合スキャン



×



ビットマップ索引単一値




×


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




×


ビットマップ・マージ




×


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



×


クラスタ・スキャン





×

ハッシュ・スキャン





×


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

表アクセス・パス

表は、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があり、これは行断片の物理アドレスに相当します。ROWIDは行の10バイトの物理アドレスです。

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

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

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


関連項目:

ROWIDの詳細は、『Oracle Database概要』を参照してください。

ダイレクト・パス読取り

ダイレクト・パス読取りでは、データベースは、SGA全体を回避しながら、ディスクからPGAに直接バッファを読み込みます。図6-1は、SGAにバッファを格納する散布読取りおよび順次読取りと、ダイレクト・パス読取りの違いを示しています。

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

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

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

  • CREATE TABLE AS SELECT文の実行

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

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

  • パラレル問合せ

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


関連項目:

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

全表スキャン

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

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

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

  • 索引が存在しない。

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

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

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

  • 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初期化パラメータの設定と同じです)、全表スキャンのほうが索引レンジ・スキャンよりもコストが低いことがあります。このスキャンは、アクセス先の表の部分や索引の存在に関係なく、よりコストが低くなる可能性があります。

  • 表の並列度が高い。

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

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

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

全表スキャンの仕組み

全表スキャンでは、データベースは、最高水位標を下回るすべてのフォーマット済ブロックを順に読み取ります。データベースは、各ブロックを1回のみ読み取ります。次の図は、表セグメントのスキャンで、最高水位標を下回る未フォーマットのブロックがどのようにスキップされるかを示しています。

cncpt371.pngの説明が続きます。
図cncpt371.pngの説明

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

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


関連項目:


全表スキャン: 例

次の文は、hr.employees表で4000を超える給与を問い合せます。

SELECT salary 
FROM   hr.employees 
WHERE  salary > 4000;

例6-1では、DBMS_XPLAN.DISPLAY_CURSOR関数を使用して計画を取得します。salary列には索引が存在しないため、オプティマイザは、索引レンジ・スキャンを使用できず、全表スキャンを使用します。

例6-1 全表スキャン

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は、バージョン間で変わってしまう場合があります。行は移動する可能性があるため、位置に基づくアクセスはお薦めしません。ROWIDの詳細は、『Oracle Database開発ガイド』を参照してください。

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

ほとんどの場合、データベースは1つ以上の索引のスキャン後にROWIDによって表にアクセスします。しかし、ROWIDによる表アクセスでは、すべての索引スキャンに従う必要はありません。必要な列がすべて索引に含まれていると、ROWIDによるアクセスは行われない場合があります(「高速全索引スキャン」を参照)。

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

ROWIDによって表にアクセスするには、データベースで次の手順を実行します。

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

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

  2. 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_percent)

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

  • SAMPLE BLOCK (sample_percent)

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

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


注意:

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


関連項目:


サンプル表スキャン: 例

例6-2は、サンプル表スキャンを使用して、行ではなくブロックによるサンプリングを行ってemployees表の1%にアクセスします。

例6-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)|
-------------------------------------------------------------------------

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

索引は、表または表クラスタに関連するオプションの構造であり、索引によってデータ・アクセスを高速化できる場合があります。表の1つ以上の列に索引を作成することによって、場合によって、ランダムに分散している行の小さなセットを表から取得できるようになります。索引は、ディスクI/Oを削減するための様々な手段のうちの1つです。

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


関連項目:

  • 索引の概要については、『Oracle Database概要』を参照してください。

  • 索引の管理方法の詳細は、『Oracle Database管理者ガイド』を参照してください。


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

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

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

図6-2 Bツリー索引構造

図6-2の説明が続きます
「図6-2 Bツリー索引構造」の説明

索引記憶が索引スキャンに与える影響

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

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


関連項目:

索引ブロックの詳細は、『Oracle Database概要』を参照してください。

一意索引と非一意索引

図6-2は、非一意索引を示しています。非一意索引では、データベースはROWIDを格納するために、キーを一意にする長さのバイト数を持つ追加列としてROWIDをキーに付加したものを格納します。たとえば、図6-2の最初の索引キーは0ROWIDの組合せであり、単に0ではありません。データは索引キーの値でソートされてから、ROWIDの昇順でソートされます。たとえば、エントリは次のように並べ替えられます。

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

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


関連項目:

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

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 |
-------------------------------------------------------------------------------
 
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で指定される行を取得します。

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

図6-3 索引一意スキャン

図6-3の説明が続きます
「図6-3 索引一意スキャン」の説明

索引一意スキャン: 例

次の文は、sh.products表の製品19のレコードを問い合せます。

SELECT * 
FROM   sh.products 
WHERE  prod_id=19;

例6-3では、DBMS_XPLAN.DISPLAY_CURSOR関数を使用して計画を取得します。products.prod_idに主キー索引が存在し、WHERE句が等価演算子を使用してすべての列を参照するため、オプティマイザは一意スキャンを選択します。

例6-3 索引一意スキャン

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を含む問合せにはレンジ・スキャンが使用され、203040といった索引キーでソートされた行が返されます。複数の索引エントリに同一のキーがある場合は、データベースはそれらをROWIDの昇順で返します。すなわち、0,AAAPvCAAFAAAAFaAAaの次に0,AAAPvCAAFAAAAFaAAgなどとなります。

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

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

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

  • 索引の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言語リファレンス』を参照してください。

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

通常のプロセスは次のとおりです。

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

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

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

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

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


    注意:

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

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

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

例6-4 索引レンジ・スキャン

図6-4の説明が続きます
「図6-4 索引レンジ・スキャン」の説明

索引レンジ・スキャン: 例

次の文は、給与が1000より多い、部門20の従業員のレコードを問い合せます。

SELECT * 
FROM   employees 
WHERE  department_id = 20
AND    salary > 1000;

例6-4では、DBMS_XPLAN.DISPLAY_CURSOR関数を使用して計画を取得します。この問合せは選択性が高く、department_id列の索引が使用されます。データベースは索引をスキャンし、employees表からレコードをフェッチした後、salary > 1000フィルタをフェッチしたレコードに適用して結果を生成します。

例6-4 索引レンジ・スキャン

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)

索引レンジ・スキャン降順: 例

次の文は、部門20の従業員のレコードを降順で問い合せます。

SELECT *
FROM   employees
WHERE  department_id < 20
ORDER BY department_id DESC;

例6-5では、DBMS_XPLAN.DISPLAY_CURSOR関数を使用して計画を取得します。この問合せは選択性が高く、department_id列の索引が使用されます。データベースは、20以下の最大キー値を含む、最初の索引リーフ・ブロックの位置を特定します。次に、スキャンはリンクされたリーフ・ノード・リストを左方向に水平に進みます。データベースは各索引エントリからROWIDを取得し、ROWIDで指定される行を取得します。

例6-5 索引レンジ・スキャン降順

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)

全索引スキャン

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

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

オプティマイザが全索引スキャンを考慮する状況には、次のようなものがあります。

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

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

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

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

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

全索引スキャンの仕組み

データベースはルート・ブロックを読み取り、リーフ・ブロックに到達するまで索引の左方向(または、降順全スキャンを行っている場合は右)に移動します。データベースは次に、一度に1ブロックずつ、ソートされた順序で下位の索引を読み取ります。スキャンは、マルチブロックI/Oではなく単一ブロックI/Oを使用します。

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

図6-5 全索引スキャン

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

全索引スキャン: 例

次の文は、部門のIDと名前を部門ID順に問い合せます。

SELECT department_id, department_name
FROM   departments
ORDER BY department_id;

例6-6では、DBMS_XPLAN.DISPLAY_CURSOR関数を使用して計画を取得します。データベースは最初の索引リーフ・ブロックを特定し、リンクされたリーフ・ノード・リストを右方向に水平に進みます。各索引エントリについて、データベースはエントリからROWIDを取得し、ROWIDで指定される表の行を取得します。このようにして、データベースは取得した行の個別のソート操作を回避します。

例6-6 全索引スキャン

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

高速全索引スキャン

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

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

問合せが索引の属性のみにアクセスする場合、オプティマイザはこのスキャンを考慮します。INDEX_FFS(table_name index_name)ヒントにより高速全索引スキャンが強制されます。


注意:

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


関連項目:

INDEXヒントSQLRF50410の詳細は、『Oracle Database SQL言語リファレンス』を参照してください。

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

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

高速全索引スキャン: 例

次の文は、部門のIDと名前を部門ID順に問い合せます。

SELECT /*+ INDEX_FFS(departments dept_id_pk) */ COUNT(*)
FROM   departments;

例6-7では、DBMS_XPLAN.DISPLAY_CURSOR関数を使用して計画を取得します。

例6-7 高速全索引スキャン

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_email)である場合、問合せ述語はcust_gender列を参照しません。

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

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

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

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

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

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

customers表にはcust_genderという列があり、この列の値はMまたはFです。次のとおり、列(cust_gendercust_email)にコンポジット索引を作成します。

CREATE INDEX customers_gender_email 
  ON sh.customers (cust_gender, cust_email);

概念上、索引の一部は、索引の先頭として性別の値にFまたはMを持つ例6-8のような形式になります。

例6-8 コンポジット索引エントリ

. . .
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索引のスキップ・スキャンを使用できます。例6-8では、先頭列cust_genderに可能な値は2つあります。データベースは索引を論理的に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                   |CUSTOMERS_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つの索引を結合する処理は次のとおりです。

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

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

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

例6-9では、DBMS_XPLAN.DISPLAY_CURSOR関数を使用して計画を取得します。データベースはemp_email_uk索引のすべてのROWIDを取得し、Aで始まる姓のemp_name_ixのROWIDを取得します。データベースはハッシュ結合を使用して、ROWIDの両方のセットから一致するものを探します。たとえば、ROWID AAAVgdAALAAAABSABDは両方のセットのROWIDに発生するので、データベースはこのROWIDに対応するレコードをemployees表から探します。

例6-9 索引結合スキャン

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

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

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

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

従来型のBツリー索引では、1つのエントリは単一の行を指します。ビットマップ索引では、ROWID範囲と組み合せた索引付けられたデータがキーです。データベースでは、索引キーごとに1つ以上のビットマップが格納されます。1および0 が連続するビットマップの各値は、ROWID範囲内の行を指します。したがって、ビットマップ索引では、1つのエントリは複数の行を指します。

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

表6-2 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範囲とビットマップが取得されます。


関連項目:

  • ビットマップ索引の概要については、『Oracle Database概要』を参照してください。

  • ビットマップ索引の詳細は、『Oracle Databaseデータ・ウェアハウス・ガイド』を参照してください。


ビットマップ索引の目的

ビットマップ索引は、頻繁に変更されないカーディナリティの低いデータに適しています。行の総数に対して列の個別値の数が少ない場合は、データのカーディナリティは低くなります。

この索引は、圧縮技法によって最小限のI/Oで多数のROWIDを生成できます。ビットマップ索引によって、次に該当する問合せで特に有効なアクセス・パスが提供されます。

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

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

  • カーディナリティの低い列でANDまたはOR演算子がある

    カーディナリティの低い列でビットマップ索引を組み合せると、これらの演算の効率が高くなります。データベースでは、ビットマップ索引から即座にビットマップを組み合せることができます。たとえば、ビットマップ索引がcustomerscust_gender列およびcust_marital_status列にある場合、これらの索引により次の問合せのパフォーマンスが大幅に向上します。

    SELECT * 
    FROM   customers 
    WHERE  cust_gender = 'M' 
    AND    cust_marital_status = 'single'
    
  • COUNT関数

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

  • NULL値を選択する述語

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


関連項目:

COUNT関数の詳細は、『Oracle Database SQL言語リファレンス』を参照してください。

ビットマップおよびROWID

ビットマップの各値は、行の値がビットマップ条件に一致する場合は1、一致しない場合は0になります。この値に基づいて、データベースの内部アルゴリズムによってビットマップがROWIDにマップされます。

ビットマップ・エントリには、索引付けられた値、ROWID範囲(開始ROWIDと終了ROWID)、およびビットマップが含まれます。ビットマップの0または1の各値は、ROWID範囲内のオフセットであり、行が存在しない場合でも、表の潜在行にマップされます。ブロック内の潜在行数は定義済のため、データベースでは、範囲のエンドポイントを使用して、範囲内の任意の行のROWIDを決定できます。


注意:

HAKAN要素は、Oracle Databaseによって単一ブロックに保存可能と見なされる行数を制限するために、ビットマップ索引アルゴリズムによって使用される最適化です。人為的に行数を制限することで、データベースでビットマップのサイズが削減されます。

表6-3は、NULL値可能なsh.customers.cust_marital_status列のサンプル・ビットマップの一部を示しています。実際の索引には12個の個別値があります。このサンプルでは、NULL、married、およびsingleの3つのみを示します。

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

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

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


表6-3のように、Bツリー索引とは異なり、ビットマップ索引は、完全にNULL値からなるキーを含む場合があります。表6-3では、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)。これにより、結果を取得するために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')

関連項目:

『Oracle Database概要』には、CREATE INDEX文が説明されています。

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

ビットマップ索引はBツリー構造で存在し、Bツリーと同じようにブランチ・ブロックおよびリーフ・ブロックを使用します。たとえば、customers.cust_marital_status列に12の個別値がある場合、1つのブランチ・ブロックがmarried,rowid-rangeキーおよびsingle,rowid-rangeキーを指し、別のブランチ・ブロックがwidowed,rowid-rangeキーを指すといった状況があります。または、単一のブランチ・ブロックが12の個別キーすべてを含むリーフ・ブロックを指す場合もあります。

索引付けられた列値には、それぞれ1つ以上のビットマップ・ピースがあり、それぞれに1つ以上のエクステントの一連の近接行を占める独自のROWID範囲があります。データベースはビットマップ・ピースを使用し、ブロックのサイズに比べて大きい索引エントリを分割できます。たとえば、データベースは単一の索引エントリが3つのピースに分割し、最初の2ピースが同じエクステントの個別ブロック、最後のピースが別のエクステントの個別ブロックに分割できます。

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

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

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

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

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

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

ビットマップの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表の問合せは、すべての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表の問合せは、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)

ビットマップ・マージ

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

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

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

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

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

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

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

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)。次に、それらのビットマップを結合して単一のビットマップを作成し(手順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)

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

表クラスタの詳細は、『Oracle Database概要』を参照してください。この項では、表クラスタのアクセス・パスについて簡単に説明します。

クラスタ・スキャン

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

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

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

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

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

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

30,AADAAAA9d

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

クラスタ・スキャン: 例

ユーザー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を使用してemployeesの行の位置を特定します(手順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概要』を参照してください。

ハッシュ・スキャン

ハッシュ・クラスタは、索引キーがハッシュ関数に置き換わることを除いて、索引付きクラスタと似ています。別個のクラスタ索引は存在しません。ハッシュ・クラスタでは、データが索引になります。ハッシュ・スキャンでは、ハッシュ値に基づいてハッシュ・クラスタ内の行の位置を特定します。

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

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

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

ハッシュ・クラスタ内においては、同一のハッシュ値を持つすべての行が同じデータ・ブロックに格納されています。ハッシュ・スキャンを実行するには、最初に、文で指定されたクラスタ・キー値にハッシュ関数を適用することによって、ハッシュ値を取得します。次に、そのハッシュ値を持つ行が含まれているデータ・ブロックをスキャンします。

クラスタ・スキャン: 例

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

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