インデックスの概要
データベース インデックスは、テーブルの特定のフィールドのデータを再編成するデータベース オブジェクトです。インデックスを使用すると、データベースの一部の操作を大幅に高速化できます。また、その背後にある考え方も非常にシンプルです。つまり、時間のためのスペースです。
データベース内の索引は書籍のカタログに似ており、書籍内の特定の情報を照会する場合、カタログを利用して対応する章をすばやく見つけることができるため、書籍全体を閲覧する必要がなく、検索のプロセスを高速化します。
インデックスの分類
Postgres の一般的なインデックスには一般に次の種類が含まれますが、その中で BTree インデックスが最も広く使用されており、インデックス作成時のデフォルトのオプションでもあります。
インデックスタイプ | インデックス名 | 説明する |
---|---|---|
ブツリー | B+ ツリー インデックス | B+ツリーで実装されたインデックスタイプは、豊富なインデックス機能(多値、ソート、クラスタリングなど)を備え、追加、削除、変更の操作性能が安定しており、広く使われているデフォルトのインデックスタイプです。 |
ハッシュ | ハッシュインデックス | ハッシュ インデックスはハッシュ テーブル構造に基づいており、等価比較クエリに適しています。ハッシュ インデックスのクエリ速度は非常に高速ですが、範囲クエリと並べ替え操作はサポートされていません。 |
ジン | ユニバーサル転置インデックス | さまざまなテキスト タイプのクエリをサポートするために使用できます。テキスト検索、配列、範囲クエリ、その他のシナリオに適しています。GIN インデックスはクエリ中に一致する値を集計するため、クエリ速度は速くなりますが、インデックスの更新と挿入の速度は遅くなります。 |
要旨 | ユニバーサル検索ツリーインデックス | GiST インデックスは、空間クエリと範囲クエリをサポートするために使用される一般的な空間インデックスです。GiST インデックスは、点、線、多角形などの複雑な空間データ タイプを処理できるため、地理情報システム (GIS) などのシナリオに適しています。GiST インデックスを使用したクエリは高速になりますが、インデックスへの更新と挿入は遅くなります。 |
インデックススキャンの例
例を使用して、テーブル スキャンのパフォーマンスに対するインデックスの影響を体験してみましょう。まず、たとえばarticleというテストテーブルを作成し、そこにテストデータを挿入します。
CREATE TABLE articles ( id SERIAL8 NOT NULL PRIMARY KEY, a text, b text, c text);INSERT INTO articles(a, b, c)SELECTmd5(random()::text),md5(random()::text),md5(random()::text)from ( SELECT * FROM generate_series(1,1000000) AS id) AS x;
このテーブルのデータをクエリします。たとえば、a = '65c966eb2be73daf418c126df8dc33b5' のデータを見つけるには、クエリ プランは次のようになります。
ここではシーケンシャルスキャン(Seq Scan)が使用されており、コスト(Cost)が22450であることがわかります。フィールド a にインデックス (デフォルトは BTree) を追加し、記事 (a) にインデックスを作成してから、この SQL ステートメントを実行すると、クエリ プランは次のようになります。 ここでインデックス スキャンが使用されていることがわかります。は 8 です。順次スキャンの 22450 と比較して、クエリのコストが大幅に削減され、クエリのパフォーマンスが大幅に向上します。
スキャン方法
シーケンシャルスキャン
クエリ オプティマイザーは、インデックスが作成されていないフィールドに対してクエリが作成された場合、またはクエリによってほとんどのデータが返されると判断された場合に、順次スキャン方式を使用します。前回の記事のテーブルを例に挙げます。ここでは、テーブル内のほとんどのデータが含まれる id > 100 のデータをクエリします。そのため、id 列にはインデックスがありますが、引き続き順次スキャンが使用されます。
インデックススキャン
クエリが非常に少量のデータにヒットすると判断された場合、クエリ オプティマイザーは、上記の例で示したインデックス スキャン方式を選択します。以下はインデックス範囲をスキャンする例です。ヒット データが占めるテーブル データの量は少なく、インデックス スキャンが最も効率的であることがわかります。
ビットマップインデックススキャン
一般にインデックス スキャンのデータは少なくなりますが、このスキャンではランダム IO 操作が必要となるため、シーケンシャル スキャンで使用されるシーケンシャル IO 操作よりもコストが常に低いとは限りません。したがって、中程度のデータ (少数または多数の間) をヒットする場合、シーケンシャル スキャンとインデックス スキャンには独自の欠点があります。この場合、通常はビットマップ インデックス スキャンが使用でき、原則として、アクセスする必要があるページを順序付けし、ランダム IO をシーケンシャル IO に変換します。
一般的な操作手順は次のとおりです。
-
インデックスを使用して、条件を満たすすべての TID をスキャンします。
-
ページアクセス順のTIDリストを使用してビットマップを構築します。
-
データ レコードを読み取る場合、同じページを 1 回読み取るだけで済みます。
次の図は、Postgres におけるいくつかのテーブル データのスキャン方法を示しています。クエリ オプティマイザーは、計算コストに応じて最適なスキャン方法を選択します。
物理ストレージのインデックス付け
postgres のインデックスはセカンダリ インデックスです。つまり、物理ストレージ上では、インデックス データと対応するテーブル データが分離されます。特定の各インデックス オブジェクトは独立したリレーショナル テーブルとして保存され、pg_class システム テーブルでクエリできます。
BTree を例にとると、その一般的な構造は次のとおりです。 B+ ツリーの一般的な特徴:
-
ツリー レベルの減少: 各内部ノードはデータを保存しなくなるため、より多くのキー値を保存できるようになり、結果としてツリー レベルが減り、クエリ データが高速化されます (ランダム IO が減少します)。
-
クエリ速度がより安定します。すべてのデータがリーフ ノードに保存されるため、各検索の回数 (ツリーの高さとランダム IO 操作の積) が同じになり、クエリ速度もより安定します。
-
クエリをトラバースする方が便利です。B+Tree のリーフ ノード データは、順序付けされたリンク リストを構成します。クエリをトラバースするときは、最初に最初のキー値の位置を見つけてから、リンク リストに沿ってすべてのデータにアクセスします。
BTree の各ノードは物理的にページとして保存され、ページの構造は次のようにヒープ テーブルの構造と似ています。
BTree を例にとると、インデックス内のコンテンツは、キー値からデータ タプル TID へのマッピングとして理解できます。TID はブロック番号とオフセットで構成されます。
インデックスの作成
ユーザーがテーブルのインデックス作成 (col) ステートメントを使用すると、構文分析や権限チェックなどの段階を経て、インデックス関係を確立し、システム メタデータを更新し、最後にテーブル内のデータを使用して完全な B-ツリーインデックス。
メイン関数の呼び出しパスは次のとおりです。
ProcessUtility() Utility语句的处理入口DefineIndex() 定义一个索引(异常判断,准备index_create()的输入参数)index_create() 创建一个索引(建立关系文件并更新系统表数据)index_build() 构建索引的外层接口bt_build() B-Tree的索引构建逻辑
プロセスユーティリティ | データベースの統合処理エントリ Utility ステートメント。インデックスを作成するために、DefineIndex 関数に転送されて処理を続行します。 |
---|---|
インデックスの定義 | 主な機能は、各種権限や異常状況の判断を行い、index_create関数に必要な各パラメータを初期化することです。 |
インデックス作成 | 主な機能は、インデックス関係とシステム テーブル レコードを確立することです。 |
インデックスビルド | インデックスのペリフェラルインターフェイスを作成し、主にインデックスに対応する ambuild 関数を呼び出します |
btbuild | BTree インデックス構築ロジック |
BTree を例にとると、テーブル内のデータを使用して B ツリー インデックスを構築する手順は、通常 2 つのステップに分かれています。1 つはテーブル内のデータを並べ替えることで、もう 1 つは BTree 全体を下から上にたどることです。順序付けされたデータタプルに。
ここでは主に、インデックスの種類ごとに異なる ambuild メソッドが呼び出されます。BTree に対応するメソッドは btbuild です。次の図は、インデックス関連インターフェイスのアクセス関係を示しています。異なるインデックス アクセス メソッドは、IndexAM によって抽象化され、上位層の実行プログラムによって呼び出されます。
インデックススキャン
エグゼキュータでのインデックス スキャンの 3 つのステップは次のとおりです。
-
ExecInitIndexScan
-
ExecIndexScan
-
ExecEndIndexScan
ExecInitIndexScan
主にインデックス スキャン状態構造体の初期化を担当します。 IndexScanState 中心となるタスクは、インデックス スキャンのフィルター条件をさまざまなタイプのスキャン キー ScanKey に変換することです。
-
ScanKey は主にインデックス列、演算関数、および比較対象の関数の情報を保存します。ScanKey は完全なフィルター条件を記述し、インデックス スキャンに使用されます。
-
ただし、フィルター条件が複雑な式の場合、それを処理するために iss_RuntimeKeys が導入されます。
IndexScanState の主なフィールド:
タイプ | 分野 | 説明する |
---|---|---|
リスト* | インデックスクオリグ | インデックスフィルター |
スキャンキーデータ | iss_ScanKeys | Qual の右手演算子は定数です |
IndexRuntimeKeyInfo | iss_RuntimeKeys | Qual の正しい演算子が定数ではなく、実行中に式の値を動的に計算する必要がある場合は、式の情報を IndexRuntimeKey に保存します。 |
Init フェーズの主な焦点は ExecIndexBuildScanKeys メソッドです。このメソッドは、スキャン フィルター条件をさまざまなタイプのスキャン キー ScanKey に変換するために使用されます。
インデックスフィルターの条件は、次の 5 つの状況に分類されます。
-
ScanKey に直接保存される定数または一般的な操作
-
非定数値式の操作 このとき、実行ノードは初期段階で式の結果を取得できないため、iss_RuntimeKeys に一時的に格納する必要があります
-
たとえば、RowCompareExpr の場合、フィルター条件は「(indexkey1, Indexkey2) > (1, 2)」です。これは、複数のフィルター条件の組み合わせを意味し、すべてのサブフィルター条件を調べて、それぞれ iss_ScanKeys または iss_RuntimeKeys に保存します。
-
たとえば、ScalarArrayOpExpr の場合、フィルター条件は「indexkey1 = ANY (1,10,20)」です。インデックスが配列ベースの検索の処理をサポートしている場合は、定数をそれぞれ ScanKey または RuntimeKey に格納します。配列検索をサポートしていない場合は、次のようになります。 Hash、GIN、Gist Index としてフィルタ条件を arrayKeys に保存します
-
NullTest は、_"indexkey IS NULL/IS NOT NULL" など、インデックス キーが NULL かどうかにかかわらず、ScanKey_ の対応する値を設定するだけです
ExecIndexScan
インデックスに基づいてタプルを読み取り、それをエグゼキュータの上位ノードに返す責任を負います。関数 IndexNext は、インデックスを継続的にスキャンし、タプルを読み取り、タプルを TupleTableSlots にカプセル化し、上位ノードに渡します。
-
この関数の主なパラメータは IndexScanDesc で、スキャン プロセス中のステータス情報を保存します。
-
xs_heap_ continue を使用して HOT チェーン上にあるかどうかを判断し、YES の場合は何もしません。
-
それ以外の場合は、index_getnext_tid を呼び出して TID を返します。
-
pg_am テーブルで amgettuple に対応する内部インターフェイス関数を検索します。
- この関数 (BTree の btgettuple など) を呼び出し、特定のインデックス実装に従って TID を返します。
- 実際のタプルを取得するには、index_fetch_heap を呼び出します。
-
ExecEndIndexScan
これは主に、クリーニング、RuntimeKey を計算するためのメモリ コンテキストの解放、および関連するインデックス テーブルとデータ テーブルのクローズを担当します。