8 オプティマイザのアクセス・パス
アクセス・パスは、問合せにより行ソースから行を取得するために使用される手法です。
8.1 「アクセス・パスの概要」
行ソースは、実行計画のステップによって戻される行のセットです。表、ビュー、または結合処理あるいはグループ化処理の結果が行ソースになる可能性があります。
アクセス・パスなどの単項演算は、行ソースから行を取得するために問合せで使用される手法であり、単一の行ソースを入力として受け入れます。たとえば、全表スキャンは、単一行ソースの行の取得です。対照的に、結合はバイナリであり、必ず2つの行ソースから入力を受け取ります。
データベースは、異なるリレーショナル・データ構造に対して異なるアクセス・パスを使用します。次の表は、主なデータ構造に対する一般的なアクセス・パスについてまとめたものです。
表8-1 データ構造とアクセス・パス
アクセス・パス | ヒープ構成表 | Bツリー索引およびIOT | ビットマップ索引 | 表クラスタ |
---|---|---|---|---|
x |
||||
x |
||||
x |
||||
x |
||||
x |
||||
x |
||||
x |
||||
x |
||||
x |
||||
x |
||||
x |
||||
x |
||||
ビットマップ索引レンジ・スキャン |
x |
|||
x |
||||
x |
オプティマイザは、使用可能な様々な実行計画を検討し、各計画にコストを割り当てます。オプティマイザは、コストが最も低い計画を選択します。一般に、表の行の小さいサブセットを取得する文には索引アクセス・パスのほうが効率的ですが、表の大きな部分にアクセスする場合は全表スキャンのほうが効率的です。
関連項目:
-
これらの構造の概要は、『Oracle Database概要』を参照してください。
8.2 表アクセス・パス
表は、Oracle Databaseにおけるデータ編成の基本単位です。
リレーショナル表は、最も一般的な表タイプです。リレーショナル表には次のような構成上の特徴があります。
-
ヒープ構成表では、行は特定の順序で格納されません。
-
索引構成表では、主キー値に従って行が整列されます。
-
外部表は読取り専用の表であり、これらのメタデータはデータベースに格納されますが、データはデータベースの外部に格納されます。
関連項目:
-
表の概要は、『Oracle Database概要』を参照してください。
-
表の管理方法を学習するには、『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にバッファを格納する散布読取りおよび順次読取りと、ダイレクト・パス読取りの違いを示しています。
Oracle Databaseがダイレクト・パス読取りを実行する可能性のある状況には、次のようなものがあります。
-
CREATE TABLE AS SELECT
文の実行 -
ALTER REBUILD
文またはALTER MOVE
文の実行 -
一時表領域からの読取り
-
パラレル問合せ
-
LOBセグメントからの読取り
関連項目:
ダイレクト・パス読取りの待機イベントについて学習するには、『Oracle Databaseパフォーマンス・チューニング・ガイド』を参照してください。
8.2.2 全表スキャン
全表スキャンでは、表のすべての行が読み取られ、選択基準を満たしていない行がフィルタ処理されます。
8.2.2.1 オプティマイザが全表スキャンを考慮する場合
一般に、オプティマイザは、別のアクセス・パスを使用できない場合、または使用可能な別のアクセス・パスのコストが高い場合は、全表スキャンを選択します。
次の表では、全表スキャンを選択する一般的な理由を示します。
表8-2 全表スキャンの一般的な理由
理由 | 説明 | さらに学習するには |
---|---|---|
索引が存在しない。 |
索引が存在しない場合、オプティマイザは、全表スキャンを使用します。 |
|
問合せ述語が、索引付けされた列に関数を適用する。 |
索引がファンクション索引でない場合、関数が適用された列の値ではなく、その列の値が索引付けされます。典型的なアプリケーション・レベルの誤りは、 |
|
|
索引にはNULLエントリを含めることはできないため、オプティマイザは、索引を使用して、表の行数をカウントすることができません。 |
「Bツリー索引およびNULL」 |
問合せ述語では、最先端のBツリー索引を使用することができない。 |
たとえば、 |
「索引スキップ・スキャン」 |
問合せは非選択性である。 |
表のほとんどのブロックを必要とする問合せの場合、オプティマイザは、索引が使用可能であっても、全表スキャンを使用します。全表スキャンではより大きなI/Oコールを使用できます。大きなI/Oコールを少なくすることにより、小さなコールを何度も行うよりもコストが低くなります。 |
|
表統計が失効している。 |
たとえば、以前は小さかった表が大きくなったとします。表統計が失効しており、表の現在のサイズを反映していない場合、オプティマイザは、現在では全表スキャンではなく索引が最も効率的であることを認識できません。 |
「オプティマイザ統計の概要」 |
表が小さい。 |
表に含まれているブロックがn個よりも少なく、最高水位標を下回っている場合(ここで、nは、 |
|
表の並列度が高い。 |
表の並列度が高いと、レンジ・スキャンよりも全表スキャンの方向にオプティマイザを偏らせます。並列度を判断するには、 |
|
問合せで全表スキャンのヒントが使用される。 |
ヒント |
8.2.2.2 全表スキャンの仕組み
全表スキャンでは、データベースは、最高水位標を下回るすべてのフォーマット済ブロックを順に読み取ります。データベースは、各ブロックを1回のみ読み取ります。
次の図は、表セグメントのスキャンで、最高水位標を下回る未フォーマットのブロックがどのようにスキップされるかを示しています。
ブロックは隣接しているため、データベースでは、I/Oコールを単一ブロックより大きくすることでスキャンを高速化できます。これは、マルチブロック読取りと呼ばれています。読取りコールのサイズは、1ブロックからDB_FILE_MULTIBLOCK_READ_COUNT
初期化パラメータで指定されているブロック数までの範囲になります。たとえば、このパラメータを4
に設定すると、データベースは単一コールで最大4ブロックを読み取ります。
全表スキャン中の、ブロックのキャッシングのアルゴリズムは複雑です。たとえば、表が小さいか大きいかによって、ブロックがキャッシュされる方法は異なります。
関連項目:
-
デフォルトのキャッシュ・モードの概要は、『Oracle Database概要』を参照してください。
-
DB_FILE_MULTIBLOCK_READ_COUNT
初期化パラメータについて学習するには、Oracle Databaseリファレンスを参照してください
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によって表にアクセスするには、データベースで複数のステップを実行します。
データベースでは、次のことが実行されます。
-
文の
WHERE
句または1つ以上の索引の索引スキャンのいずれかを使用して、選択した行のROWIDを取得します。索引内に存在しない文の中の列には、表アクセスが必要になる場合があります。
-
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
のヒントを使用します。
関連項目:
-
SAMPLE
句について学習するには、Oracle Database SQL言語リファレンスを参照してください
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_AWARE
をFALSE
に設定した場合と同じです。
インメモリー問合せを有効または無効にするには、INMEMORY
またはNO_INMEMORY
ヒントを指定できます。これは、問合せごとのINMEMORY_QUERY
初期化パラメータと同じです。SQL文でINMEMORY
ヒントを使用し、それが参照するオブジェクトがIM列ストアにまだロードされていない場合、データベースは文を実行する前にIM列ストアにオブジェクトが移入されるのを待機しません。ただし、オブジェクトの初期アクセスにより、IM列ストア内のオブジェクトの移入がトリガーされます。
関連項目:
-
INMEMORY_QUERY
、OPTIMIZER_INMEMORY_AWARE
およびOPTIMIZER_FEATURES_ENABLE
初期化パラメータについてさらに学習するには、Oracle Databaseリファレンスを参照してください -
INMEMORY
ヒントについてさらに学習するには、Oracle Database SQL言語リファレンスを参照してください
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つです。
関連項目:
-
索引の概要は、Oracle Database概要を参照してください
-
自動索引作成と手動索引作成についてさらに学習するには、『Oracle Database管理者ガイド』を参照
8.3.1 Bツリー索引アクセスについて
Bツリー(バランス・ツリーの略)は、最も一般的なタイプのデータベース索引です。
Bツリー索引は、複数の範囲に分割された順序付きの値リストです。Bツリーは、キーを行または行の範囲と関連付けることによって、完全一致や範囲検索など、広範囲の問合せに対して優れた検索パフォーマンスを提供します。
8.3.1.1 Bツリー索引構造
Bツリー索引には、検索用のブランチ・ブロックと、値を格納するリーフ・ブロックの2種類のブロックがあります。
次の図は、Bツリー索引の論理構造を示しています。ブランチ・ブロックには、2つのキーの分岐を決定する際に必要な、最小のキー接頭辞が格納されます。リーフ・ブロックには、すべての索引付きデータ値と、実際の行を検索するための対応するROWIDが含まれています。各索引エントリは(キー、ROWID)によってソートされます。リーフ・ブロックは二重にリンクされます。
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は含まれません。データは、0
、1
、2
といった索引キーの値のみでソートされます。
関連項目:
一意および非一意の索引の概要は、『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
)
ヒントを使用して使用する索引を指定できますが、特定の索引アクセス・パスは指定できません。
関連項目:
-
索引構造の詳細とBツリーの検索方法の詳細は、『Oracle Database概要』を参照してください。
-
INDEX
ヒントについてさらに学習するには、Oracle Database SQL言語リファレンスを参照してください
8.3.2.2 索引一意スキャンの仕組み
このスキャンでは、指定したキーの索引を順番に検索します。索引の一意スキャンでは、2つ目のレコードがないことがわかっているため、最初のレコードが見つかるとただちに処理が停止します。データベースは索引エントリからROWIDを取得し、ROWIDで指定される行を取得します。
次の図は、索引一意スキャンを示しています。文は、主キー索引があるprod_id
列の製品IDが19
のレコードをリクエストします。
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
を含む問合せにはレンジ・スキャンが使用され、20
、30
、40
といった索引キーでソートされた行が返されます。複数の索引エントリに同一のキーがある場合は、データベースはそれらをROWIDの昇順で返します。すなわち、0,AAAPvCAAFAAAAFaAAa
の次に0,AAAPvCAAFAAAAFaAAg
などとなります。
索引レンジ・スキャン降順は、行が降順で戻されること以外、索引レンジ・スキャンと同じです。通常、降順スキャンが使用されるのは、データを降順に並べる場合か、指定された値より小さい値を検索する場合です。
8.3.3.1 オプティマイザが索引レンジ・スキャンを考慮する場合
索引レンジ・スキャンの場合、複数の値が索引キーに使用できる必要があります。
特に、オプティマイザは、次の状況で索引レンジ・スキャンを検討します。
-
索引の1つ以上の先頭列を条件に指定します。
条件は、1つ以上の式および論理(ブール)演算子の組合せを指定し、
TRUE
、FALSE
またはUNKNOWN
の値を戻します。次に、条件の例を示します。-
department_id = :id
-
department_id < :id
-
department_id > :id
-
索引の先頭列用の前述の条件の
AND
組合せ(department_id > :low AND department_id < :hi
など)。ノート:
オプティマイザがレンジ・スキャンを考慮するために、
col1 LIKE '%ASD'
の形式のワイルド・カード検索は先頭に指定しないでください。
-
-
0個、1個、または複数の値を索引キーにできます。
ヒント:
ソートされたデータが必要な場合は、索引には依存せず、ORDER
BY
句を使用します。索引でORDER
BY
句を満たすことが可能な場合、オプティマイザはこのオプションを使用し、これによってソートを回避します。
索引でORDER BY DESCENDING
句を満たすことが可能な場合、オプティマイザは索引レンジ・スキャン降順を考慮します。
オプティマイザが全表スキャンまたは別の索引を選択する場合、このアクセス・パスを強制するためにヒントが必要になる可能性があります。INDEX(
tbl_alias
ix_name
)
ヒントおよびINDEX_DESC(
tbl_alias
ix_name
)
ヒントは、特定の索引を使用するようにオプティマイザに指示します。
関連項目:
INDEX
およびINDEX_DESC
ヒントについてさらに学習するには、『Oracle Database SQL言語リファレンス』を参照してください
8.3.3.2 索引レンジ・スキャンの仕組み
索引レンジ・スキャン中、Oracle Databaseはルートからブランチに進みます。
一般に、スキャン・アルゴリズムは次のとおりです。
-
ルート・ブロックを読み取ります。
-
ブランチ・ブロックを読み取ります。
-
すべてのデータが取り出されるまで、次のステップを交互に行います。
-
リーフ・ブロックを読み取って、ROWIDを取得します。
-
表ブロックを読み取って、行を取得します。
ノート:
場合によっては、索引スキャンは索引ブロックのセットを読み取り、ROWIDをソートしてから、表ブロックのセットを読み取ります。
-
そのため、索引をスキャンするには、データベースではリーフ・ブロックを後方または前方に移動します。たとえば、20から40のIDを求めるスキャンでは、最小キー値20以上を含む最初のリーフ・ブロックが検索されます。40より大きい値が見つかるまで、スキャンはリンクされたリーフ・ノード・リストを水平に進み、その後停止します。
次の図は、昇順を使用した索引レンジ・スキャンを示しています。文は、department_id
列(非一意索引を含む)の値が20
のemployees
レコードをリクエストします。この例では、部門20
には2つの索引エントリが存在します。
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.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 索引スキップ・スキャン
索引スキップ・スキャンは、コンポジット索引の初期列が問合せでスキップされる(指定されない)場合に行われます。
関連項目:
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_gender
はWHERE
句で指定されていませんが、データベースでは、customers_gender_email
索引のスキップ・スキャンを使用できます。サンプルの索引では、先頭列cust_gender
に可能な値は2つ(F
およびM
)あります。データベースは索引を論理的に2つに分割します。1つ目の副索引は、次の形式のエントリで、キーF
を持ちます。
F,Wolf@company.example.com,rowid
F,Wolsey@company.example.com,rowid
F,Wood@company.example.com,rowid
F,Woodman@company.example.com,rowid
F,Yang@company.example.com,rowid
F,Zimmerman@company.example.com,rowid
2つ目の副索引は、次の形式のエントリで、キーM
を持ちます。
M,Abbassi@company.example.com,rowid
M,Abbey@company.example.com,rowid
電子メールがAbbey@company.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つの索引を結合する処理は次のとおりです。
-
最初の索引をスキャンしてROWIDを取得します。
-
2番目の索引をスキャンしてROWIDを取得します。
-
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 |
|
一意ではないBツリー |
ROWIDと組み合せた索引付けられたデータ |
なし |
|
ビットマップ |
ROWID範囲と組み合せた索引付けられたデータ |
ビットマップ |
|
データベースでは、ビットマップ索引をBツリー構造に格納します。データベースでは、索引が定義される属性のセットであるキーの最初の部分のBツリーが即座に検索され、対応するROWID範囲とビットマップが取得されます。
関連項目:
-
ビットマップ索引の概要は、『Oracle Database概要』を参照してください。
-
ビットマップ索引の詳細は、『Oracle Databaseデータ・ウェアハウス・ガイド』を参照してください。
8.4.1.2 ビットマップ索引の目的
ビットマップ索引は一般に、個別値の数(NDV)が少ない、または中程度の頻繁に変更されないデータに適しています。
一般に、Bツリー索引は、NDVが多く、DMLアクティビティが頻繁に発生する列に適しています。たとえば、オプティマイザは、少数の行を返すsales.amount
列の問合せについてBツリー索引を選択する可能性があります。これに対して、customers.state
列とcustomers.county
列は、個別値が少なく、頻繁に更新されないため、効率的なAND
演算およびOR
演算の利点を得られることから、ビットマップ索引の候補となります。
ビットマップ索引は、データ・ウェアハウス内の非定型問合せを高速化するために有用な方法です。これらは、スター型変換の基盤となります。特に、ビットマップ索引は次を含む問合せで効果的です。
-
WHERE
句に複数の条件があるすべての条件ではなく一部の条件のみを満たす行は、表自体がアクセスされる前にデータベースによって除外されます。
-
NDVが少ない、または中程度の列に対する
AND
、OR
および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
まで変更できません。
関連項目:
-
COUNT
関数について学習するには、Oracle Database SQL言語リファレンスを参照してください
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) |
|
|
0 |
0 |
0 |
0 |
0 |
1 |
|
|
|
1 |
0 |
1 |
1 |
1 |
0 |
|
|
|
0 |
1 |
0 |
0 |
0 |
0 |
|
|
|
1 |
0 |
1 |
0 |
1 |
1 |
表8-4のように、Bツリー索引とは異なり、ビットマップ索引は、完全にNULL値からなるキーを含む場合があります。表8-4では、NULLにおいて範囲内の6番目の行の値が1
です。これは、範囲内の6番目の行は、cust_marital_status
値がNULLであることを意味します。NULLの索引作成は、集計関数COUNT
による問合せなど、一部のSQL文に使用できます。
関連項目:
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)。これにより、customers
とsales
表の結合はなくなります。
SQL_ID 57s100mh142wy, child number 0
-------------------------------------
SELECT COUNT (*) FROM sales s, customers c WHERE c.cust_id = s.cust_id
AND c.cust_city = 'Smithville'
Plan hash value: 3663491772
------------------------------------------------------------------------------------
|Id| Operation | Name |Rows|Bytes|Cost (%CPU)| Time|Pstart|Pstop|
------------------------------------------------------------------------------------
| 0| SELECT STATEMENT | | | |29 (100)| | | |
| 1| SORT AGGREGATE | | 1 | 5| | | | |
| 2| PARTITION RANGE ALL | | 1708|8540|29 (0)|00:00:01|1|28|
| 3| BITMAP CONVERSION COUNT | | 1708|8540|29 (0)|00:00:01| | |
|*4| BITMAP INDEX SINGLE VALUE|CUST_SALES_BJI| | | | |1|28|
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("S"."SYS_NC00008$"='Smithville')
関連項目:
CREATE INDEX
文について学習するには、Oracle Database概要を参照してください
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.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.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番目の行 |
---|---|---|---|---|---|---|---|---|
|
|
|
0 |
1 |
0 |
0 |
0 |
0 |
|
|
|
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番目の行 |
---|---|---|---|---|---|---|---|---|
|
|
|
0 |
0 |
0 |
0 |
0 |
1 |
|
|
|
1 |
0 |
1 |
1 |
1 |
0 |
|
|
|
0 |
1 |
0 |
0 |
0 |
0 |
|
|
|
1 |
0 |
1 |
0 |
1 |
1 |
関連項目:
8.4.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.2 ビットマップ・マージの仕組み
結合では、2つのビットマップ間でブールOR
演算が使用されます。結果のビットマップでは、最初のビットマップのすべての行が選択され、さらに後続のすべてのビットマップのすべての行も選択されます。
問合せが1918年より前に生まれたすべての顧客を選択する場合があります。次の例では、3つのcustomers.cust_year_of_birth
キー1917
、1916
、および1915
のサンプル・ビットマップを示します。ビットマップのいずれかの位置に1
があれば、結合ビットマップの同じ位置が1
になります。それ以外の場合は、結合ビットマップは0
になります。
1917 1 0 1 0 0 0 0 0 0 0 0 0 0 1
1916 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1915 0 0 0 0 0 0 0 0 1 0 0 0 0 0
------------------------------------
merged: 1 1 1 0 0 0 0 0 1 0 0 0 0 1
結果のビットマップにある1
の値が、1915
、1916
または1917
の値を含む行に対応します。
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)。データベースは、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)
8.5 表クラスタ・アクセス・パス
表クラスタとは、共通の列を共有し、関連するデータを同じブロックに格納する表のグループのことです。表がクラスタ化されると、1つのデータ・ブロックに複数の表の行を含めることができます。
関連項目:
表クラスタの概要は、『Oracle Database概要』を参照してください。
8.5.1 クラスタ・スキャン
索引クラスタとは、索引を使用してデータを検索する表クラスタのことです。
クラスタ索引とは、クラスタ・キーのBツリー索引のことです。クラスタ・スキャンでは、索引クラスタに格納された表から、同じクラスタ・キー値を持つすべての行が取得されます。
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.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概要』を参照してください。