PostgreSQLでのインデックスの機能


73

PostgreSQLでのインデックスの動作に関していくつか質問があります。Friends次のインデックスを持つテーブルがあります。

   Friends ( user_id1 ,user_id2) 

user_id1そしてテーブルuser_id2への外部キーですuser

  1. これらは同等ですか?そうでない場合、なぜですか?

    Index(user_id1,user_id2) and Index(user_id2,user_id1)
  2. 主キー(user_id1、user_id2)を作成すると、自動的にインデックスが作成され、

    最初の質問のインデックスが同等でない場合、上記のプライマリキーコマンドでどのインデックスが作成されますか?

回答:


77

以下は、マルチカラムインデックスの2番目のカラムでテーブルをクエリした結果です。
エフェクトは誰でも簡単に再現できます。自宅で試してみてください。

Debian でPostgreSQL 9.0.5を使用して、23322行の実際のデータベースの中規模テーブルを使用してテストしました。テーブルadr(アドレス)とatt(属性)の間のn:m関係を実装しますが、ここでは関係ありません。簡略化されたスキーマ:

CREATE TABLE adratt (
  adratt_id serial PRIMARY KEY
, adr_id    integer NOT NULL
, att_id    integer NOT NULL
, log_up    timestamp(0) NOT NULL DEFAULT (now())::timestamp(0)
, CONSTRAINT adratt_uni UNIQUE (adr_id, att_id)
);

UNIQUE制約は、効果的に一意のインデックスを実装しています。単純なインデックスを使用してテストを繰り返し、確実に同じ結果を得ました。

CREATE INDEX adratt_idx ON adratt(adr_id, att_id)

テーブルはadratt_uniインデックス上にクラスター化されており、テストの前に実行しました。

CLUSTER adratt;
ANALYZE adratt;

クエリの順次スキャンは(adr_id, att_id)、可能な限り高速です。複数列インデックスは、2番目のインデックス列のみのクエリ条件に引き続き使用されます。

クエリを数回実行してキャッシュにデータを入力し、10回の実行からベストを選択して比較可能な結果を​​得ました。

1.両方の列を使用したクエリ

SELECT *
FROM   adratt
WHERE  att_id = 90
AND    adr_id = 10;

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       123 |     10 |     90 | 2008-07-29 09:35:54
(1 row)

の出力EXPLAIN ANALYZE

Index Scan using adratt_uni on adratt  (cost=0.00..3.48 rows=1 width=20) (actual time=0.022..0.025 rows=1 loops=1)
  Index Cond: ((adr_id = 10) AND (att_id = 90))
Total runtime: 0.067 ms

2.最初の列を使用したクエリ

SELECT * FROM adratt WHERE adr_id = 10

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       126 |     10 |     10 | 2008-07-29 09:35:54
       125 |     10 |     13 | 2008-07-29 09:35:54
      4711 |     10 |     21 | 2008-07-29 09:35:54
     29322 |     10 |     22 | 2011-06-06 15:50:38
     29321 |     10 |     30 | 2011-06-06 15:47:17
       124 |     10 |     62 | 2008-07-29 09:35:54
     21913 |     10 |     78 | 2008-07-29 09:35:54
       123 |     10 |     90 | 2008-07-29 09:35:54
     28352 |     10 |    106 | 2010-11-22 12:37:50
(9 rows)

の出力EXPLAIN ANALYZE

Index Scan using adratt_uni on adratt  (cost=0.00..8.23 rows=9 width=20) (actual time=0.007..0.023 rows=9 loops=1)
  Index Cond: (adr_id = 10)
Total runtime: 0.058 ms

3. 2番目の列を使用したクエリ

SELECT * FROM adratt WHERE att_id = 90

 adratt_id | adr_id | att_id |       log_up
-----------+--------+--------+---------------------
       123 |     10 |     90 | 2008-07-29 09:35:54
       180 |     39 |     90 | 2008-08-29 15:46:07
...
(83 rows)

の出力EXPLAIN ANALYZE

Index Scan using adratt_uni on adratt  (cost=0.00..818.51 rows=83 width=20) (actual time=0.014..0.694 rows=83 loops=1)
  Index Cond: (att_id = 90)
Total runtime: 0.849 ms

4.インデックススキャンとビットマップスキャンを無効にします

SET enable_indexscan = off;
SELECT * FROM adratt WHERE att_id = 90

EXPLAIN ANALYZEの出力:

Bitmap Heap Scan on adratt  (cost=779.94..854.74 rows=83 width=20) (actual time=0.558..0.743 rows=83 loops=1)
  Recheck Cond: (att_id = 90)
  ->  Bitmap Index Scan on adratt_uni  (cost=0.00..779.86 rows=83 width=0) (actual time=0.544..0.544 rows=83 loops=1)
        Index Cond: (att_id = 90)
Total runtime: 0.894 ms
SET enable_bitmapscan = off
SELECT * FROM adratt WHERE att_id = 90

の出力EXPLAIN ANALYZE

Seq Scan on adratt  (cost=0.00..1323.10 rows=83 width=20) (actual time=0.009..2.429 rows=83 loops=1)
  Filter: (att_id = 90)
Total runtime: 2.680 ms

結論

予想どおり、複数列のインデックスは、2番目の列のみのクエリに使用されます。
予想どおり、効果は劣りますが、クエリはインデックスなしの場合よりも3倍高速です。
インデックススキャンを無効にした後、クエリプランナーはビットマップヒープスキャンを選択します。これはほぼ同じ速度で実行されます。それも無効にした後にのみ、順次スキャンにフォールバックします。


クラスタリングがしますインデックス内の一致の数が十分に高い場合の違いを作る(参照ここで証明するために-データをキャッシュを取得するために、二重の実行に注意してください)
ジャック・ダグラス

1
@JackDouglas:これについてもう少し考えました。クラスタリング、実質的にa vacuum fullおよびa でもあるため、一般的に役立ちますreindex。それ以外は、最初の列または両方の主要な列でのインデックススキャンに大いに役立ちますが、2番目の列でのクエリを傷つけます。新たにクラスター化されたテーブルでは、2番目の列に同じ値を持つ行が分散されるため、最大のブロックを読み取る必要があります。
アーウィンブランドステッター

28

日時1)はい、いいえ。

両方の列を使用するクエリの場合、たとえば、where (user_id1, user_id2) = (1,2)どのインデックスが作成されるかは関係ありません。

where user_id1 = 1通常、オプティマイザーによる比較には「先頭の」列のみを使用できるため、列の1つのみに条件があるクエリの場合は重要です。そのwhere user_id1 = 1ため、インデックス(user_id1、user_id2)を使用できますが、すべてのケースでインデックス(user_id2、user_id1)を使用することはできません。

これで遊んだ後(Erwinが動作するセットアップを親切に示してから)、これは2番目の列のデータ分布に大きく依存しているようですが、どの状況でオプティマイザが後続の列を使用できるかはまだわかりませんWHERE条件の場合。

Oracle 11は、インデックス定義の先頭にない列も使用できる場合があります。

re 2)はい、インデックスを作成します

マニュアルから引用

主キーを追加すると、主キーで使用される列または列グループに一意のbtreeインデックスが自動的に作成されます。

2A RE)Primary Key (user_id1,user_id2)自分で見つけることができます(user_id1、user_id2)(上のインデックスを作成します非常に単純な主キーを作成することによって、容易)

マニュアルのインデックスに関する章を読むことを強くお勧めします。これは基本的に上記のすべての質問に答えます。

また、作成するインデックス何ですか?by depeszは、インデックス列とその他のインデックス関連トピックの順序を説明するのに適しています。


11

広告1)
PostgreSQLには@a_horse_with_no_nameが記述するような制限があります。バージョン8.0までのマルチカラムインデックスは、先行カラムのクエリにのみ使用できました。これはバージョン8.1で改善されました。Postgres 10(更新済み)の現在のマニュアルでは次のことが説明されています。

複数列のBツリーインデックスは、インデックスの列のサブセットを含むクエリ条件で使用できますが、先頭(左端)の列に制約がある場合にインデックスが最も効率的です。厳密な規則は、先行列の等式制約に加えて、等式制約のない最初の列の不等式制約を使用して、スキャンされるインデックスの部分を制限することです。これらの列の右側の列の制約はインデックスでチェックされるため、テーブルへの訪問を適切に保存しますが、スキャンする必要のあるインデックスの部分は削減しません。たとえば、インデックスon (a, b, c)およびクエリ条件が与えられたWHERE a = 5 AND b >= 42 AND c < 77場合、インデックスはa= 5の最初のエントリからスキャンする必要があり、b= 42、最後のエントリまでa=5。インデックスエントリがc77以上の場合はスキップされますが、スキャンする必要があります。原則として、このインデックスは、制約のあるクエリbおよび/またはc制約のないクエリに使用できますが、インデックスa全体をスキャンする必要があるため、ほとんどの場合、プランナーはインデックスの使用よりもシーケンシャルテーブルスキャンを好むでしょう。

強調鉱山。私は経験からそれを確認できます。
また、テストケースを参照してください後の回答はこちらです。


11

これは、ジャックの答えへの返信であり、コメントはしません。

バージョン9.2より前のPostgreSQLには、カバーするインデックスがありませんでした。MVCCモデルのため、結果セット内のすべてのタプルを参照して、可視性を確認する必要があります。Oracleを考えているかもしれません。

PostgreSQL開発者は「インデックスのみのスキャン」について話します。実際、この機能はPostgres 9.2でリリースされました。コミットメッセージを読んでください
Depeszは非常に有益なブログ記事を書きました。

INCLUDEPostgres 11 では、真のカバーリングインデックス(更新)が句と共に導入されています。

これも少しずれています。

インデックスに表示されない余分な列があるため、インデックスの「フルスキャン」はインデックス付きテーブルの「フルスキャン」よりも速いことが多いという事実に依存しています。

私の他の答えに関するコメントで報告されているように、2つの整数のテーブルでテストを実行しましたが、他には何もありません。インデックスは、テーブルと同じ列を保持します。btreeインデックスのサイズは、テーブルのサイズの約2/3です。要因3の高速化を説明するのに十分ではありません。設定に基づいて、2列に簡略化して100000行のテストをさらに実行しました。PostgreSQL 9.0のインストールでは、結果に一貫性がありました。

テーブルに追加の列がある場合、インデックスによる高速化はより大幅になりますが、それがここでの唯一の要因ではないことは確かです

主なポイントを要約するには:

  • 複数列のインデックスは、先頭以外の列のクエリで使用できますが、選択基準の場合、スピードアップは約3倍になります(結果の行の割合が小さい)。タプルが大きいほど高く、結果セット内のテーブルの大きい部分が低くなります。

  • パフォーマンスが重要な場合は、これらの列に追加のインデックスを作成します。

  • 関連するすべての列がインデックスに含まれ(インデックスをカバー)、関連するすべての行(ブロックごと)がすべてのトランザクションに表示される場合、9.2以降で「インデックスのみのスキャン」を取得できます。


7
  1. これらは同等ですか?そうでない場合、なぜですか?

    Index(user_id1、user_id2)およびIndex(user_id2、user_id1)

これらは同等ではなく、一般的に言えば、index(bar、baz)は次の形式のクエリに対して効率的ではありません。 select * from foo where baz=?

アーウィンそのようなインデックスが実際にクエリを高速化できることを実証しましたが、この効果は制限され、インデックスがルックアップを改善するために一般的に期待するのと同じ順序ではありません-それはインデックスの「フルスキャン」がしばしばテーブル内の余分な列がインデックスに表示されないため、インデックス付きテーブルの「フルスキャン」よりも高速です。

要約:インデックスは、先頭以外の列でもクエリに役立ちますが、2つの二次的な比較的マイナーな方法の1つであり、btree構造のためにインデックスが通常劇的に役立つと期待する方法ではありません

NBインデックスのフルスキャンがテーブルのフルスキャンよりもかなり安いですし、どちらかの場合、インデックスは助けることができる2つの方法がある:1。(そこにそれらのいくつかがあるか、それらがクラスタ化されているので)、テーブルルックアップが安価であり、または2.インデックスがされてカバーするので、全くのテーブルのルックアップはありませんおっと、Erwinsのコメントを参照してくださいここでは、

テストベッド:

create table foo(bar integer not null, baz integer not null, qux text not null);

insert into foo(bar, baz, qux)
select random()*100, random()*100, 'some random text '||g from generate_series(1,10000) g;

クエリ1(インデックスなし、74バッファをヒット):

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=181.41..181.42 rows=1 width=32) (actual time=3.301..3.302 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..181.30 rows=43 width=32) (actual time=0.043..3.228 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.335 ms

クエリ2(インデックスあり-オプティマイザはインデックスを無視します- 再度74バッファをヒットします):

create index bar_baz on foo(bar, baz);

explain (buffers, analyze, verbose) select max(qux) from foo where baz=0;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=199.12..199.13 rows=1 width=32) (actual time=3.277..3.277 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=74
   ->  Seq Scan on stack.foo  (cost=0.00..199.00 rows=50 width=32) (actual time=0.043..3.210 rows=52 loops=1)
         Output: bar, baz, qux
         Filter: (foo.baz = 0)
         Buffers: shared hit=74
 Total runtime: 3.311 ms

クエリ2(インデックス付き-オプティマイザをだまして使用する):

explain (buffers, analyze, verbose) select max(qux) from foo where bar>-1000 and baz=0;
                                                       QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=115.56..115.57 rows=1 width=32) (actual time=1.495..1.495 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=36 read=30
   ->  Bitmap Heap Scan on stack.foo  (cost=73.59..115.52 rows=17 width=32) (actual time=1.370..1.428 rows=52 loops=1)
         Output: bar, baz, qux
         Recheck Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
         Buffers: shared hit=36 read=30
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..73.58 rows=17 width=0) (actual time=1.356..1.356 rows=52 loops=1)
               Index Cond: ((foo.bar > (-1000)) AND (foo.baz = 0))
               Buffers: shared read=30
 Total runtime: 1.535 ms

したがって、この場合、インデックスを介したアクセスは2倍速くなり、30個のバッファにヒットします -インデックス作成に関しては「わずかに高速」です!YMMVは、テーブルとインデックスの相対的なサイズ、フィルタリングされた行の数およびクラスタリング特性に依存しますテーブル内のデータの

対照的に、先頭の列に対するクエリはインデックスのbtree構造を利用します-この場合は2つのバッファをヒットします

explain (buffers, analyze, verbose) select max(qux) from foo where bar=0;
                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=75.70..75.71 rows=1 width=32) (actual time=0.172..0.173 rows=1 loops=1)
   Output: max(qux)
   Buffers: shared hit=38
   ->  Bitmap Heap Scan on stack.foo  (cost=4.64..75.57 rows=50 width=32) (actual time=0.036..0.097 rows=59 loops=1)
         Output: bar, baz, qux
         Recheck Cond: (foo.bar = 0)
         Buffers: shared hit=38
         ->  Bitmap Index Scan on bar_baz  (cost=0.00..4.63 rows=50 width=0) (actual time=0.024..0.024 rows=59 loops=1)
               Index Cond: (foo.bar = 0)
               Buffers: shared hit=2
 Total runtime: 0.209 ms
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.