🔍

PostgreSQLで外部キー制約が付与された列にインデックスがあるかを調べる

に公開

PostgreSQLでは、外部キー制約を付与した列に自動でインデックスは作成されないため、必要に応じて明示的にインデックスを作成する必要があります。

このインデックスを作成するかどうかの方針については、Should I Create an Index on Foreign Keys in PostgreSQL?でも書かれているように、「パフォーマンス向上が見込まれる場合のみ作成する方針」や、「漏れが怖いため全て作成する方針」など、プロジェクトやチームによって様々だと思います。ただ、どのような方針を取るにせよ、設計と実際のインデックス作成状況のズレを検知する作業はしばしば発生します。この作業はテーブル数が多くなってくると目視で行うのはあまり現実的ではなくなってきます。

そこで、この作業を自動化するために、外部キー制約が付与された列にインデックスが存在しているかを一覧で確認できるSQLを書いてみました。

SQLの内容については、コメントで補足説明を入れています。なお、複数列からなる外部キーには非対応となっています(今後の課題)。

WITH base AS (
    SELECT
        cons.conname,
        cons.conkey,
        cons.conrelid  AS src_rel_id,
        attr1.attname  AS src_attr_name,
        cons.confrelid AS dst_rel_id,
        attr2.attname  AS dst_attr_name
    FROM
        pg_constraint cons
            -- conkeyに参照元の列番号があるので、pg_attributeとJOINしてテーブル名と列名を取得する。
            -- = ANYなのはconkeyがarrayのため。
            JOIN pg_attribute attr1
                 ON attr1.attrelid = cons.conrelid AND attr1.attnum = ANY(cons.conkey)
            -- confkeyに参照先の列番号があるので、pg_attributeとJOINしてテーブル名と列名を取得する。
            --  = ANYなのはconfkeyがarrayのため。
            JOIN pg_attribute attr2
                 ON attr2.attrelid = cons.confrelid AND attr2.attnum = ANY(cons.confkey)
    WHERE
        -- pg_constraintテーブルのcontype = 'f'(Foreign Key)で外部キー制約のみを取得。
        cons.contype = 'f'
        -- conkey, confkeyの長さをチェックして複数列からなる外部キー制約を除外
        AND (array_length(cons.conkey, 1) = 1 OR array_length(cons.confkey, 1) = 1)
)
SELECT
    base.conname AS 外部キー制約名,
    base.src_rel_id::regclass AS 参照元テーブル名,
    base.src_attr_name AS 参照元カラム名,
    base.dst_rel_id::regclass AS 参照先テーブル名,
    base.dst_attr_name AS 参照先カラム名,
    COALESCE (
        (
            SELECT
                string_agg(idx.indexrelid::regclass::text, ' , ')
            FROM
                pg_index idx
            WHERE
                idx.indrelid = base.src_rel_id
                -- 部分インデックスと関数インデックスは除外する
                AND indpred IS NULL
                AND indexprs IS NULL
                -- 外部キー列がインデックスの先頭にあるかを判定する
                -- - indkeyはint2vectorで0始まり
                -- - conkeyはarrayで1始まり
                AND idx.indkey[0] = base.conkey[1]
        ),
        '無し'
    ) AS 外部キー制約の列が先頭にあるインデックス
FROM
    base
;

なお、RLSを利用しているケースで、先頭にそのためのキー(以下の例ではtenant_id)があるインデックスも確認したい場合は以下のような列を追加すると確認できます。ただし、RLSが無効な条件下ではインデックスが使われず意図しない負荷が発生するケースがあるため、 そのようなインデックスには注意が必要です。

    COALESCE (
        (
            SELECT
                string_agg(idx.indexrelid::regclass::text, ' , ')
            FROM
                pg_index idx
                    JOIN pg_attribute attr
                        ON idx.indrelid = attr.attrelid AND idx.indkey[0] = attr.attnum
            WHERE
                idx.indrelid = base.src_rel_id
                AND idx.indpred IS NULL
                AND idx.indexprs IS NULL
                AND attr.attname = 'tenant_id'
                AND idx.indkey[1] = base.conkey[1]
        ),
        '無し'
    ) AS tenant_idが先頭で外部キー列が2番目にあるインデックス,

Discussion