外部キーと主キーのPostgresとインデックス


342

Postgresは自動的に外部キーと主キーにインデックスを付けますか?どうすればわかりますか?テーブルのすべてのインデックスを返すコマンドはありますか?

回答:


405

PostgreSQLは、主キーと一意制約のインデックスを自動的に作成しますが、外部キー関係の参照側には作成しません。

Pgが暗黙のインデックスを作成すると、NOTICE-levelメッセージが表示されpsql、システムログで確認できるため、いつ発生したかを確認できます。自動作成されたインデックスは\d、テーブルの出力にも表示されます。

ユニークインデックス上のドキュメントは言います:

PostgreSQLは一意性を強制するために、一意性制約と主キー制約ごとにインデックスを自動的に作成します。したがって、主キー列に対して明示的に索引を作成する必要はありません。

制約に関するドキュメントは言う:

参照されるテーブルからの行のDELETEまたは参照される列のUPDATEでは、古い値に一致する行を参照するテーブルをスキャンする必要があるため、参照する列にインデックスを付けることをお勧めします。これは常に必要なわけではなく、インデックスの作成方法には多くの選択肢があるため、外部キー制約を宣言しても、参照する列にインデックスが自動的に作成されるわけではありません。

したがって、必要に応じて、外部キーにインデックスを自分で作成する必要があります。

M-to-NテーブルのPKとして2 FKのような主外部キーを使用する場合、PKにインデックスがあり、おそらく追加のインデックスを作成する必要がないことに注意してください。

通常、参照側の外部キー列に(またはそれを含めて)インデックスを作成することをお勧めしますが、必須ではありません。追加するインデックスごとにDML操作がわずかに遅くなるためINSERTUPDATEまたはごとにパフォーマンスコストが発生しDELETEます。インデックスがほとんど使用されない場合、その価値はないかもしれません。


26
この編集に問題がないことを願っています。関連するドキュメントへのリンクを追加しました。FKリレーションシップの参照側が暗黙的なインデックスを生成しないことを完全に明示する引用、psqlでインデックスを表示する方法を示し、明確にするために最初の部分を言い換え、追加しました。インデックスは無料ではないので、追加することが常に適切であるとは限らないことに注意してください。
クレイグリンガー

1
@CraigRinger、インデックスのメリットがコストを上回るかどうかをどのように判断しますか?インデックスを追加する前後に単体テストのプロファイルを作成し、全体的なパフォーマンスの向上を確認しますか?それとももっと良い方法がありますか?
ギリ2014年

2
@Giliこれは、dba.stackexchange.comに関する別の質問のトピックです。
クレイグリンガー

34

プログラムからスキーマ内のすべてのテーブルのインデックスを一覧表示する場合、すべての情報がカタログにあります。

select
     n.nspname  as "Schema"
    ,t.relname  as "Table"
    ,c.relname  as "Index"
from
          pg_catalog.pg_class c
     join pg_catalog.pg_namespace n on n.oid        = c.relnamespace
     join pg_catalog.pg_index i     on i.indexrelid = c.oid
     join pg_catalog.pg_class t     on i.indrelid   = t.oid
where
        c.relkind = 'i'
    and n.nspname not in ('pg_catalog', 'pg_toast')
    and pg_catalog.pg_table_is_visible(c.oid)
order by
     n.nspname
    ,t.relname
    ,c.relname

さらに詳しく調べたい場合(列や順序など)、pg_catalog.pg_indexを調べる必要があります。を使用psql -E [dbname]すると、カタログを照会する方法を理解するのに便利です。


4
+1 pg_catalogおよびpsql -Eの使用は本当に非常に役立つため
Ghislain Leveque

「参照のため\diに、データベース内のすべてのインデックスもリストされます。」(コメントは他の回答からコピーされ、ここにも適用されます)
Risadinha

33

このクエリは、元のソースである外部キーの不足しているインデックスリストします

編集:小さなテーブル(9 MB未満)やその他のケースはチェックしないことに注意してください。最後のWHEREステートメントを参照してください。

-- check for FKs where there is no matching index
-- on the referencing side
-- or a bad index

WITH fk_actions ( code, action ) AS (
    VALUES ( 'a', 'error' ),
        ( 'r', 'restrict' ),
        ( 'c', 'cascade' ),
        ( 'n', 'set null' ),
        ( 'd', 'set default' )
),
fk_list AS (
    SELECT pg_constraint.oid as fkoid, conrelid, confrelid as parentid,
        conname, relname, nspname,
        fk_actions_update.action as update_action,
        fk_actions_delete.action as delete_action,
        conkey as key_cols
    FROM pg_constraint
        JOIN pg_class ON conrelid = pg_class.oid
        JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid
        JOIN fk_actions AS fk_actions_update ON confupdtype = fk_actions_update.code
        JOIN fk_actions AS fk_actions_delete ON confdeltype = fk_actions_delete.code
    WHERE contype = 'f'
),
fk_attributes AS (
    SELECT fkoid, conrelid, attname, attnum
    FROM fk_list
        JOIN pg_attribute
            ON conrelid = attrelid
            AND attnum = ANY( key_cols )
    ORDER BY fkoid, attnum
),
fk_cols_list AS (
    SELECT fkoid, array_agg(attname) as cols_list
    FROM fk_attributes
    GROUP BY fkoid
),
index_list AS (
    SELECT indexrelid as indexid,
        pg_class.relname as indexname,
        indrelid,
        indkey,
        indpred is not null as has_predicate,
        pg_get_indexdef(indexrelid) as indexdef
    FROM pg_index
        JOIN pg_class ON indexrelid = pg_class.oid
    WHERE indisvalid
),
fk_index_match AS (
    SELECT fk_list.*,
        indexid,
        indexname,
        indkey::int[] as indexatts,
        has_predicate,
        indexdef,
        array_length(key_cols, 1) as fk_colcount,
        array_length(indkey,1) as index_colcount,
        round(pg_relation_size(conrelid)/(1024^2)::numeric) as table_mb,
        cols_list
    FROM fk_list
        JOIN fk_cols_list USING (fkoid)
        LEFT OUTER JOIN index_list
            ON conrelid = indrelid
            AND (indkey::int2[])[0:(array_length(key_cols,1) -1)] @> key_cols

),
fk_perfect_match AS (
    SELECT fkoid
    FROM fk_index_match
    WHERE (index_colcount - 1) <= fk_colcount
        AND NOT has_predicate
        AND indexdef LIKE '%USING btree%'
),
fk_index_check AS (
    SELECT 'no index' as issue, *, 1 as issue_sort
    FROM fk_index_match
    WHERE indexid IS NULL
    UNION ALL
    SELECT 'questionable index' as issue, *, 2
    FROM fk_index_match
    WHERE indexid IS NOT NULL
        AND fkoid NOT IN (
            SELECT fkoid
            FROM fk_perfect_match)
),
parent_table_stats AS (
    SELECT fkoid, tabstats.relname as parent_name,
        (n_tup_ins + n_tup_upd + n_tup_del + n_tup_hot_upd) as parent_writes,
        round(pg_relation_size(parentid)/(1024^2)::numeric) as parent_mb
    FROM pg_stat_user_tables AS tabstats
        JOIN fk_list
            ON relid = parentid
),
fk_table_stats AS (
    SELECT fkoid,
        (n_tup_ins + n_tup_upd + n_tup_del + n_tup_hot_upd) as writes,
        seq_scan as table_scans
    FROM pg_stat_user_tables AS tabstats
        JOIN fk_list
            ON relid = conrelid
)
SELECT nspname as schema_name,
    relname as table_name,
    conname as fk_name,
    issue,
    table_mb,
    writes,
    table_scans,
    parent_name,
    parent_mb,
    parent_writes,
    cols_list,
    indexdef
FROM fk_index_check
    JOIN parent_table_stats USING (fkoid)
    JOIN fk_table_stats USING (fkoid)
WHERE table_mb > 9
    AND ( writes > 1000
        OR parent_writes > 1000
        OR parent_mb > 10 )
ORDER BY issue_sort, table_mb DESC, table_name, fk_name;

7
動作していないようです。ドメインテーブルを参照するインデックスのない列があることがわかっている場合、0行を返します。
juanitogan

6
@juanitogan where条項を確認する:とりわけ、9 MBを超えるサイズが考慮されるのはテーブルのみです。
Matthias

@マティアス-ああ、それを得た。ありがとう。ええ、私は明らかにコードを読むのに時間はかかりませんでした。気にするほど重要ではなかった。OPは制限に言及することができます。たぶん、またチェックするよ。
juanitogan 2017年

@SergeyBは、主キー制約が設定された参照列に誤検知を与えるように見えるため、自動的にインデックスが設定されますが、クエリはそれらにフラグを立てます。
Debasish Mitra 2018


14

これが「EclipseLink 2.5のクールなパフォーマンス機能」の記事で説明されている方法が気に入っています

外部キーのインデックス作成

最初の機能は、外部キーの自動インデックスです。ほとんどの人は、データベースがデフォルトで外部キーにインデックスを付けると誤って想定しています。まあ、そうではありません。主キーには自動インデックスが作成されますが、外部キーにはありません。つまり、外部キーに基づくクエリはすべてテーブルスキャンを実行します。これは、任意のあるOneToMany多対多またはElementCollection関係だけでなく、多くの OneToOneの 関係、及び結合やオブジェクトの比較関わるすべての関係上ほとんどのクエリ。これは主要なパフォーマンスの問題になる可能性があり、外部キーフィールドには常にインデックスを付ける必要があります。


5
常に外部キーフィールドにインデックスを付ける必要がある場合、データベースエンジンが既にそれを行わないのはなぜですか?私には、見た目よりもこれ以上のものがあるようです。
Bobort 2017年

3
@Bobortインデックスを追加すると、すべての挿入、更新、削除でパフォーマンスが低下するため、この場合、多くの外部キーが追加される可能性があります。そのため、この動作はオプトインであると思います-開発者はこの問題について意識的に選択する必要があります。外部キーがデータの整合性を強制するために使用されているが、頻繁に照会されない、またはまったく照会されない場合もあります。この場合、インデックスのパフォーマンスペナルティはまったくありません
Dr.Strangelove

3
複合インデックスは左から右に適用されるため、トリッキーなケースもあります。つまり、コメントテーブルの[user_id、article_id]の複合インデックスは、ユーザーによるすべてのコメントのクエリ(Webサイトに集約されたコメントログを表示するなど)とすべてのフェッチの両方を効果的にカバーします。このユーザーが特定の記事に対して行ったコメント。この場合、user_idに個別のインデックスを追加すると、挿入、更新、削除の際のディスク領域とCPU時間の無駄になります。
Dr.Strangelove

2
ああ!その後、アドバイスは貧弱です!外部キーに常にインデックスを付ける必要はありません。@ Dr.Strangeloveが指摘したように、実際にはそれらをインデックス化したくない場合があります!どうもありがとう、博士!
Bobort

デフォルトでインデックスが作成されないのはなぜですか?これを必要とする重要なユースケースはありますか?
Adam Arold

7

の場合、PRIMARY KEYインデックスは次のメッセージで作成されます。

NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "index" for table "table" 

FOREIGN KEYreferencのにはインデックスが存在しない場合、制約は作成されませんエドテーブルが。

referencに索引INGの表は、(所望のが)必要とされないので、暗黙的に作成されません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.