web-dev-qa-db-ja.com

PostgreSQLでのインデックスの操作

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

   Friends ( user_id1 ,user_id2) 

user_id1およびuser_id2userテーブルへの外部キーです

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

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

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

80
codecool

マルチカラムインデックスの2番目のカラムでテーブルをクエリした結果は次のとおりです。
効果は誰にとっても簡単に再現できます。家で試してみてください。

私はPostgreSQLでテストしました9.0.5 Debianで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番目の列のみのクエリに使用されます。
予想どおり、効果は低くなりますが、クエリはインデックスなしの場合よりも倍高速です。
インデックススキャンを無効にした後、クエリプランナーはビットマップヒープスキャンを選択します。これは、ほぼ同じ速度で実行されます。それも無効にした後でのみ、順次スキャンにフォールバックします。

83

re 1)はい、いいえ。

両方の列を使用するクエリの場合。 where (user_id1, user_id2) = (1,2)どのインデックスを作成してもかまいません。

1つの列のみに条件があるクエリの場合。 _where user_id1 = 1_それは問題ではありません。通常、オプティマイザによる比較には「先行」列のみを使用できるためです。したがって、_where user_id1 = 1_はインデックス(user_id1、user_id2)を使用できますが、すべてのケースでインデックス(user_id2、user_id1)を使用できるわけではありません。

これを試してみた後(Erwinが機能する設定を親切に教えてくれた後)、オプティマイザが後続列を使用できるようにする状況はまだわかりませんが、これは2列目のデータ分布に大きく依存しているようですWHERE条件の場合。

Oracle 11は、(場合によっては)インデックス定義の先頭にない列も使用できます。

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

マニュアルからの引用

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

re 2a)Primary Key (user_id1,user_id2)は(user_id1、user_id2)にインデックスを作成します(これは自分でvery簡単に見つけることができます)そのような主キーの作成)

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

さらに、 作成するインデックスは? by depeszは、インデックス列の順序とその他のインデックス関連トピックについて説明しています。

30

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

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

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

12

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

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

PostgreSQL開発者は"index-only scans]について話します。実際、この機能はPostgres 9.2でリリースされています。 コミットメッセージを読む
Depeszは 非常に有益なブログ投稿 を書きました。

True Covering Index(更新)は、Postgres 11のINCLUDE句で導入されました。

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

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

私の他の回答のコメントで報告されているように、私は2つの整数のテーブルのみを使用してテストも実行しました。インデックスはテーブルと同じ列を保持します。 btreeインデックスのサイズは、テーブルのサイズの約2/3です。ファクター3のスピードアップを説明するには不十分です。セットアップに基づいて、2つの列と100000行に簡略化したテストをさらに実行しました。私のPostgreSQL 9.0インストールでは、結果に一貫性がありました。

テーブルに追加の列がある場合、インデックスによるスピードアップはより重要になりますが、-ここで唯一の要因ではありません

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

  • 複数列インデックスは、非主要列のクエリで使用できますが、選択した基準(結果の行のパーセンテージが小さい)の場合、スピードアップは係数3にすぎません。大きなタプルの場合は高く、結果セットのテーブルの大きな部分の場合は低くなります。

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

  • 関連するすべての列がインデックス(カバーするインデックス)に含まれていて、関連するすべての行(ブロックごと)がすべてのトランザクションから見える場合、pg-9.2以降で "index-only scan" を取得できます。

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

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

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

Erwin 実証済み このようなインデックスは実際にクエリを高速化できますが、この効果は制限されており、インデックスがルックアップを向上させると通常期待するのと同じ順序ではありません。インデックスに表示されないテーブル内の追加の列があるため、インデックスのスキャンはインデックス付きテーブルの「フルスキャン」よりも高速であることがよくあります。

要約:主要でない列でもインデックスはクエリに役立ちますが、btree構造が原因で通常インデックスが役立つと劇的な方法ではなく、2つのセカンダリで比較的マイナーな方法の1つで

nbインデックスが役立つ2つの方法は、インデックスのフルスキャンがテーブルのフルスキャンよりも大幅に安価であり、次のいずれかである場合です。1。テーブルルックアップが安価(それらが少ないか、またはクラスター化されているため)、 または2.インデックスは covering であるため、テーブル検索はまったくありません。 おっと、Erwinsのコメントを参照 here

テストベッド:

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