バージョンごとのドキュメント一覧

11.9. インデックスオンリースキャンとカバリングインデックス #

<title>Index-Only Scans and Covering Indexes</title>

All indexes in <productname>PostgreSQL</productname> are <firstterm>secondary</firstterm> indexes, meaning that each index is stored separately from the table's main data area (which is called the table's <firstterm>heap</firstterm> in <productname>PostgreSQL</productname> terminology). This means that in an ordinary index scan, each row retrieval requires fetching data from both the index and the heap. Furthermore, while the index entries that match a given indexable <literal>WHERE</literal> condition are usually close together in the index, the table rows they reference might be anywhere in the heap. The heap-access portion of an index scan thus involves a lot of random access into the heap, which can be slow, particularly on traditional rotating media. (As described in <xref linkend="indexes-bitmap-scans"/>, bitmap scans try to alleviate this cost by doing the heap accesses in sorted order, but that only goes so far.) PostgreSQLにおけるすべてのインデックスは二次的なインデックス、つまり各インデックスはテーブルの主要なデータ領域(PostgreSQLの用語ではテーブルのヒープと呼ばれます)とは別に格納されています。 このことは、通常のインデックススキャンにおいて、各行の検索にはインデックスとヒープの両方からデータを取得する必要があることを意味します。 さらに、指定のインデックス可能なWHERE条件に適合するインデックスのエントリは、通常、インデックス内の近い位置にあるのに対し、そこから参照されるテーブルの行はヒープ内のあらゆるところにあるかもしれません。 このため、インデックススキャンにおけるヒープアクセスの部分では、ヒープに対する多くのランダムアクセスがありますが、これは遅い可能性があり、特に伝統的な回転型メディアでは遅くなります。 (11.5で説明したように、ビットマップインデックスはヒープアクセスをソートした順で行うことでこのコストを緩和しようとするものですが、それはある程度までしかできません。)

To solve this performance problem, <productname>PostgreSQL</productname> supports <firstterm>index-only scans</firstterm>, which can answer queries from an index alone without any heap access. The basic idea is to return values directly out of each index entry instead of consulting the associated heap entry. There are two fundamental restrictions on when this method can be used: このパフォーマンス問題を解決するため、PostgreSQLインデックスオンリースキャンをサポートします。 これは、問い合わせに対してヒープアクセスをせずにインデックスのみで回答できるものです。 基本的な考え方は、関連するヒープのエントリを参照せずに、各インデックスエントリから直接に値を返すというものです。 この方法が使用できるためには2つの基本的な制限があります。

  1. The index type must support index-only scans. B-tree indexes always do. GiST and SP-GiST indexes support index-only scans for some operator classes but not others. Other index types have no support. The underlying requirement is that the index must physically store, or else be able to reconstruct, the original data value for each index entry. As a counterexample, GIN indexes cannot support index-only scans because each index entry typically holds only part of the original data value. インデックスの種類がインデックスオンリースキャンをサポートしている必要があります。 B-treeインデックスはいつでもインデックスオンリースキャンをサポートしています。 GiSTとSP-GiSTは一部の演算子クラスでインデックスオンリースキャンをサポートしていますが、サポートしない演算子クラスもあります。 他のインデックスの種類はインデックスオンリースキャンをサポートしていません。 根本的な必要条件は、インデックスが各インデックスのエントリに対応する元のデータ値を物理的に格納していなければならない、あるいはそれを再構築できる必要がある、ということです。 その反例として、GINインデックスでは、各インデックスエントリが通常は元のデータ値の一部しか保持していないため、インデックスオンリースキャンをサポートすることができません。

  2. The query must reference only columns stored in the index. For example, given an index on columns <literal>x</literal> and <literal>y</literal> of a table that also has a column <literal>z</literal>, these queries could use index-only scans: 問い合わせはインデックスに格納されている列だけを参照しなければなりません。 例えばテーブルの列xyにインデックスがあり、そのテーブルにはさらに列zがある場合、次の問い合わせはインデックスオンリースキャンを使用できます。

    SELECT x, y FROM tab WHERE x = 'key';
    SELECT x FROM tab WHERE x = 'key' AND y < 42;
    

    but these queries could not: しかし、以下の問い合わせはインデックスオンリースキャンを使用できません。

    SELECT x, z FROM tab WHERE x = 'key';
    SELECT x FROM tab WHERE x = 'key' AND z < 42;
    

    (Expression indexes and partial indexes complicate this rule, as discussed below.) (以下で説明するように、式インデックスや部分インデックスは、この規則を複雑にします。)

If these two fundamental requirements are met, then all the data values required by the query are available from the index, so an index-only scan is physically possible. But there is an additional requirement for any table scan in <productname>PostgreSQL</productname>: it must verify that each retrieved row be <quote>visible</quote> to the query's MVCC snapshot, as discussed in <xref linkend="mvcc"/>. Visibility information is not stored in index entries, only in heap entries; so at first glance it would seem that every row retrieval would require a heap access anyway. And this is indeed the case, if the table row has been modified recently. However, for seldom-changing data there is a way around this problem. <productname>PostgreSQL</productname> tracks, for each page in a table's heap, whether all rows stored in that page are old enough to be visible to all current and future transactions. This information is stored in a bit in the table's <firstterm>visibility map</firstterm>. An index-only scan, after finding a candidate index entry, checks the visibility map bit for the corresponding heap page. If it's set, the row is known visible and so the data can be returned with no further work. If it's not set, the heap entry must be visited to find out whether it's visible, so no performance advantage is gained over a standard index scan. Even in the successful case, this approach trades visibility map accesses for heap accesses; but since the visibility map is four orders of magnitude smaller than the heap it describes, far less physical I/O is needed to access it. In most situations the visibility map remains cached in memory all the time. この2つの基本的な要件が満たされるなら、問い合わせで要求されるすべてのデータ値はインデックスから利用できるので、インデックスオンリースキャンが物理的に可能になります。 しかし、PostgreSQLのすべてのテーブルスキャンにおいて、さらなる必要条件があります。 それは、第13章で説明するように、検索された各行が問い合わせのMVCCスナップショットに対して可視であることを確認しなければならない、ということです。 可視性の情報はインデックスのエントリには格納されず、ヒープのエントリにのみあります。 そのため、一見すると、すべての行検索はいずれにせよヒープアクセスが必要なように思われます。 そして、テーブルの行が最近に更新された場合は、まさにその通りなのです。 しかし、あまり更新されないデータについてはこの問題を回避する方法があります。 PostgreSQLではテーブルのヒープの各ページについて、そのページに格納されているすべての行が、十分に古く、すべての現在および将来のトランザクションに対して可視であるかどうかを追跡しています。 この情報はテーブルの可視性マップのビットに格納されます。 インデックスオンリースキャンでは、候補となるインデックスのエントリを見つけた後、対応するヒープページの可視性マップのビットを検査します。 それがセットされていれば、行が可視であることがわかるので、それ以上の作業をすることなく、データを返すことができます。 セットされていない場合は、それが可視かどうかを調べるためにヒープエントリにアクセスする必要があり、そのため標準的なインデックススキャンに対するパフォーマンス上の利点はありません。 うまくいく場合であっても、この方法はヒープアクセスと引き換えに可視性マップにアクセスします。 しかし、可視性マップはヒープに比べ、4桁の規模で小さいため、アクセスに必要な物理的I/Oははるかに少ないです。 ほとんどの状況では、可視性マップは常にメモリ内にキャッシュされて残っています。

In short, while an index-only scan is possible given the two fundamental requirements, it will be a win only if a significant fraction of the table's heap pages have their all-visible map bits set. But tables in which a large fraction of the rows are unchanging are common enough to make this type of scan very useful in practice. 要するに、2つの基本的条件が満たされていればインデックスオンリースキャンが可能ですが、テーブルのヒープページのかなりの部分に対し、その全可視のビットがセットされている場合にのみ、性能が向上します。 しかし大部分の行が変化しないテーブルは一般的であり、現実にはこの種のスキャンは非常に有効です。

To make effective use of the index-only scan feature, you might choose to create a <firstterm>covering index</firstterm>, which is an index specifically designed to include the columns needed by a particular type of query that you run frequently. Since queries typically need to retrieve more columns than just the ones they search on, <productname>PostgreSQL</productname> allows you to create an index in which some columns are just <quote>payload</quote> and are not part of the search key. This is done by adding an <literal>INCLUDE</literal> clause listing the extra columns. For example, if you commonly run queries like インデックスオンリースキャンの機能を有効に利用するため、カバリングインデックスの作成を選択できます。 これは、頻繁に実行する特定の種類の問い合わせに必要な列を含めるように特別に設計されたインデックスです。 問い合わせは通常、検索対象の列よりも多くの列を取得する必要があるため、PostgreSQLはいくつかの列を単にペイロードとして検索キーの一部ではないインデックスを作成できます。 これは追加の列リストをINCLUDE句に加えることで実行出来ます。 例えば、次のような問い合わせをよく実行する場合を考えます。

SELECT y FROM tab WHERE x = 'key';

the traditional approach to speeding up such queries would be to create an index on <literal>x</literal> only. However, an index defined as このような問い合わせを高速化する伝統的な手法は、xのみにインデックスを作成することです。 しかし、次のようなインデックス定義では、

CREATE INDEX tab_x_y ON tab(x) INCLUDE (y);

could handle these queries as index-only scans, because <literal>y</literal> can be obtained from the index without visiting the heap. yはヒープにアクセスしなくてもインデックスから取得できるため、この問い合わせをインデックスオンリースキャンとして処理できます。

Because column <literal>y</literal> is not part of the index's search key, it does not have to be of a data type that the index can handle; it's merely stored in the index and is not interpreted by the index machinery. Also, if the index is a unique index, that is y列はインデックスの検索キーの一部ではないため、インデックスが処理できるデータ型である必要はありません。 単にインデックスに格納されているだけで、インデックス機構によって解釈されることはありません。 また、インデックスが一意インデックスの場合は、

CREATE UNIQUE INDEX tab_x_y ON tab(x) INCLUDE (y);

the uniqueness condition applies to just column <literal>x</literal>, not to the combination of <literal>x</literal> and <literal>y</literal>. (An <literal>INCLUDE</literal> clause can also be written in <literal>UNIQUE</literal> and <literal>PRIMARY KEY</literal> constraints, providing alternative syntax for setting up an index like this.) x列のみに一意性条件が適用されます。 xyの組み合わせではありません。 (INCLUDE句は、インデックスを設定するための代替構文を提供するUNIQUEPRIMARY KEYの制約として書くことも出来ます。)

It's wise to be conservative about adding non-key payload columns to an index, especially wide columns. If an index tuple exceeds the maximum size allowed for the index type, data insertion will fail. In any case, non-key columns duplicate data from the index's table and bloat the size of the index, thus potentially slowing searches. And remember that there is little point in including payload columns in an index unless the table changes slowly enough that an index-only scan is likely to not need to access the heap. If the heap tuple must be visited anyway, it costs nothing more to get the column's value from there. Other restrictions are that expressions are not currently supported as included columns, and that only B-tree, GiST and SP-GiST indexes currently support included columns. キー以外のペイロード列、特に幅の広い列をインデックスに追加することについては慎重になることが賢明です。 インデックス型の最大サイズを超えるタプルをインサートしようとすると失敗します。 いかなる場合でもキー以外の列が重複データだったり、インデックスサイズが膨張すると、検索が遅くなる可能性があります。 それから、覚えておくべきもう一つの小さなポイントは、インデックスオンリースキャンがヒープへのアクセスが必要がないほどテーブルがゆっくり変化しない限り、インデックスにペイロード列を含めることにほとんど意味が無いことです。 とにかくヒープタプルを訪れなければならなくなった場合は、そこから列値を取得するためにそれ以上のコストはかかりません。 他の制限は、式を列に含めることは、現在のところサポートされていません。また、列を含めるサポートは現在のところB-treeとGiSTとSP-GiSTインデックスのみサポートされています。

Before <productname>PostgreSQL</productname> had the <literal>INCLUDE</literal> feature, people sometimes made covering indexes by writing the payload columns as ordinary index columns, that is writing INCLUDE機能がない以前のPostgreSQLでは、ペイロード列を通常のインデックス列としてカバリングインデックスを作成することがありました。

CREATE INDEX tab_x_y ON tab(x, y);

even though they had no intention of ever using <literal>y</literal> as part of a <literal>WHERE</literal> clause. This works fine as long as the extra columns are trailing columns; making them be leading columns is unwise for the reasons explained in <xref linkend="indexes-multicolumn"/>. However, this method doesn't support the case where you want the index to enforce uniqueness on the key column(s). これは、yWHERE句の一部で使用するつもりがなかったとしても書いています。 余分な列が末尾の列である限り、これはうまく機能します。 それらを先頭側の列にすることは、11.3で説明されている理由から賢明ではありません。 しかし、この方法では、キー列に一意性を強制するインデックスがサポートされません。

<firstterm>Suffix truncation</firstterm> always removes non-key columns from upper B-Tree levels. As payload columns, they are never used to guide index scans. The truncation process also removes one or more trailing key column(s) when the remaining prefix of key column(s) happens to be sufficient to describe tuples on the lowest B-Tree level. In practice, covering indexes without an <literal>INCLUDE</literal> clause often avoid storing columns that are effectively payload in the upper levels. However, explicitly defining payload columns as non-key columns <emphasis>reliably</emphasis> keeps the tuples in upper levels small. 末尾消去は、常に上位のB-treeレベルから非キーの列を削除します。 ペイロード列として、それらはインデックススキャンを導くためには使われません。 また、この消去プロセスは、キー列の残りのプレフィックスが、最下位のB-treeレベルのタプルを記述するのに十分である場合、1つ以上の後続キー列も削除します。 実際上、INCLUDE句を使用しないカバリングインデックスは、実質的に上位レベルにペイロードが含まれるカラムの格納を避けられます。 ただし、ペイロード列を非キー列として明示的に定義すると確実に上位レベルのタプルが小さくなります。

In principle, index-only scans can be used with expression indexes. For example, given an index on <literal>f(x)</literal> where <literal>x</literal> is a table column, it should be possible to execute 原則として、インデックスオンリースキャンは式インデックスでも使うことができます。 例えば、xがテーブルの列で、f(x)上にインデックスがある場合、次の問い合わせをインデックスオンリースキャンとして実行できるはずです。

SELECT f(x) FROM tab WHERE f(x) < 1;

as an index-only scan; and this is very attractive if <literal>f()</literal> is an expensive-to-compute function. However, <productname>PostgreSQL</productname>'s planner is currently not very smart about such cases. It considers a query to be potentially executable by index-only scan only when all <emphasis>columns</emphasis> needed by the query are available from the index. In this example, <literal>x</literal> is not needed except in the context <literal>f(x)</literal>, but the planner does not notice that and concludes that an index-only scan is not possible. If an index-only scan seems sufficiently worthwhile, this can be worked around by adding <literal>x</literal> as an included column, for example そして、関数f()の計算が高価なら、この方法は非常に魅力的です。 しかしPostgreSQLのプランナは現在のところ、このような場合についてあまり賢くありません。 プランナは、問い合わせで必要となるすべてのがインデックスから利用可能な場合にのみ、その問い合わせが潜在的にインデックスオンリースキャンで実行可能と考えます。 この例では、f(x)という文脈でしかxは必要になりませんが、プランナはそのことに気付かないため、インデックスオンリースキャンは不可能であると結論します。 インデックスオンリースキャンは十分に価値があると思われるなら、含める列としてxを追加することで回避できます。 例をあげます。

CREATE INDEX tab_f_x ON tab (f(x)) INCLUDE (x);

An additional caveat, if the goal is to avoid recalculating <literal>f(x)</literal>, is that the planner won't necessarily match uses of <literal>f(x)</literal> that aren't in indexable <literal>WHERE</literal> clauses to the index column. It will usually get this right in simple queries such as shown above, but not in queries that involve joins. These deficiencies may be remedied in future versions of <productname>PostgreSQL</productname>. 目的がf(x)の再計算を避けることの場合、さらなる注意として、プランナはインデックス可能なWHERE句にないf(x)の使用を必ずしもインデックス列とマッチしないという事があります。 上記のような単純な問い合わせの場合は通常は正しく処理できるでしょうが、結合を含む問い合わせでは駄目でしょう。 これらの欠点はPostgreSQLの将来のバージョンで解決されるかもしれません。

Partial indexes also have interesting interactions with index-only scans. Consider the partial index shown in <xref linkend="indexes-partial-ex3"/>: 部分インデックスもインデックスオンリースキャンとの間に興味深い関係があります。 例 11.3に示す部分インデックスを考えます。

CREATE UNIQUE INDEX tests_success_constraint ON tests (subject, target)
    WHERE success;

In principle, we could do an index-only scan on this index to satisfy a query like 原則として、次のような問い合わせに対して、このインデックスを使ったインデックスオンリースキャンが可能です。

SELECT target FROM tests WHERE subject = 'some-subject' AND success;

But there's a problem: the <literal>WHERE</literal> clause refers to <literal>success</literal> which is not available as a result column of the index. Nonetheless, an index-only scan is possible because the plan does not need to recheck that part of the <literal>WHERE</literal> clause at run time: all entries found in the index necessarily have <literal>success = true</literal> so this need not be explicitly checked in the plan. <productname>PostgreSQL</productname> versions 9.6 and later will recognize such cases and allow index-only scans to be generated, but older versions will not. しかし、WHERE句で参照されるsuccessがインデックスの結果列として利用できないという問題があります。 それにも関わらず、インデックスオンリースキャンが可能です。 なぜなら、このプランではWHERE句のその部分を実行時に再検査する必要がない、つまりインデックス内にあるすべてのエントリは必ずsuccess = trueなので、プラン内でこれを明示的に検査する必要がないからです。 PostgreSQLのバージョン9.6およびそれ以降ではこのような場合を認識し、インデックスオンリースキャンを生成可能ですが、それより古いバージョンではできません。