Postgres und Indizes für Fremdschlüssel und Primärschlüssel

342

Setzt Postgres automatisch Indizes für Fremdschlüssel und Primärschlüssel? Wie kann ich sagen? Gibt es einen Befehl, der alle Indizes für eine Tabelle zurückgibt?

Hauptstringargs
quelle

Antworten:

405

PostgreSQL erstellt automatisch Indizes für Primärschlüssel und eindeutige Einschränkungen, jedoch nicht für die referenzierende Seite von Fremdschlüsselbeziehungen.

Wenn Pg einen impliziten Index erstellt, wird eine NOTICENachricht auf Ebene ausgegeben, die Sie in psqlund / oder in den Systemprotokollen sehen können, damit Sie sehen können, wann dies geschieht. Automatisch erstellte Indizes werden auch in der \dAusgabe einer Tabelle angezeigt.

In der Dokumentation zu eindeutigen Indizes heißt es:

PostgreSQL erstellt automatisch einen Index für jede eindeutige Einschränkung und Primärschlüsseleinschränkung, um die Eindeutigkeit zu erzwingen. Daher ist es nicht erforderlich, einen Index explizit für Primärschlüsselspalten zu erstellen.

und die Dokumentation zu Einschränkungen lautet:

Da ein LÖSCHEN einer Zeile aus der referenzierten Tabelle oder ein UPDATE einer referenzierten Spalte einen Scan der referenzierenden Tabelle nach Zeilen erfordert, die dem alten Wert entsprechen, ist es häufig eine gute Idee, die referenzierenden Spalten zu indizieren. Da dies nicht immer erforderlich ist und viele Möglichkeiten zum Indizieren zur Verfügung stehen, wird durch die Deklaration einer Fremdschlüsseleinschränkung nicht automatisch ein Index für die referenzierenden Spalten erstellt.

Daher müssen Sie selbst Indizes für Fremdschlüssel erstellen, wenn Sie diese möchten.

Beachten Sie, dass Sie bei Verwendung von Primär-Fremdschlüsseln wie 2 FKs als PK in einer M-zu-N-Tabelle einen Index für die PK haben und wahrscheinlich keine zusätzlichen Indizes erstellen müssen.

Während es normalerweise eine gute Idee ist, einen Index für Ihre referenzseitigen Fremdschlüsselspalten zu erstellen (oder einzuschließen), ist dies nicht erforderlich. Jeder Index , den Sie hinzufügen verlangsamt Operationen leicht nach unten DML, so dass Sie auf Kosten der Leistung auf jeder zahlen INSERT, UPDATEoder DELETE. Wenn der Index selten verwendet wird, lohnt es sich möglicherweise nicht, ihn zu haben.

Philipp
quelle
26
Ich hoffe, diese Bearbeitung ist in Ordnung. Ich habe Links zur relevanten Dokumentation hinzugefügt, ein Zitat, das deutlich macht, dass die referenzierende Seite von FK-Beziehungen keinen impliziten Index erzeugt, gezeigt, wie Indizes in psql angezeigt werden, das 1. Par aus Gründen der Klarheit umformuliert und a hinzugefügt Beachten Sie, dass Indizes nicht kostenlos sind und es daher nicht immer richtig ist, sie hinzuzufügen.
Craig Ringer
1
@CraigRinger, wie stellen Sie fest, ob der Nutzen eines Index seine Kosten übersteigt? Profiliere ich Unit-Tests vor / nach dem Hinzufügen eines Index und überprüfe sie auf einen allgemeinen Leistungsgewinn? Oder gibt es einen besseren Weg?
Gili
2
@Gili Das ist ein Thema für eine separate Frage zu dba.stackexchange.com.
Craig Ringer
34

Wenn Sie die Indizes aller Tabellen in Ihren Schemas aus Ihrem Programm auflisten möchten, finden Sie alle Informationen im Katalog:

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

Wenn Sie weiter vertiefen möchten (z. B. Spalten und Reihenfolge), müssen Sie sich pg_catalog.pg_index ansehen. Die Verwendung ist psql -E [dbname]praktisch, um herauszufinden, wie der Katalog abgefragt wird.

dland
quelle
4
+1, weil die Verwendung von pg_catalog und psql -E wirklich sehr nützlich ist
Ghislain Leveque
"Als Referenz \diwerden auch alle Indizes in der Datenbank aufgelistet." (Kommentar aus anderer Antwort kopiert, gilt auch hier)
Risadinha
33

Diese Abfrage listet fehlende Indizes für Fremdschlüssel , Originalquelle, auf .

Bearbeiten : Beachten Sie, dass kleine Tabellen (weniger als 9 MB) und einige andere Fälle nicht überprüft werden. Siehe Schlusserklärung 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;
SergeyB
quelle
7
Scheint nicht zu funktionieren. Gibt 0 Zeilen zurück, wenn ich weiß, dass ich Spalten ohne Indizes habe, die auf Domänentabellen verweisen.
Juanitogan
6
@juanitogan Beachten Sie die whereKlauseln: Unter anderem werden nur Tabellen berücksichtigt, deren Größe mehr als 9 MB beträgt.
Matthias
@ Matthias - Ah, verstanden. Vielen Dank. Ja, ich habe mir offensichtlich keine Zeit genommen, den Code durchzulesen. Es war nicht kritisch genug, um sich darum zu kümmern. Das OP hätte die Einschränkungen erwähnen können. Vielleicht werde ich es irgendwann noch einmal überprüfen.
Juanitogan
@SergeyB Es scheint falsch positiv für Spalten zu sein, auf die verwiesen wird, die eine Primärschlüsseleinschränkung haben, und somit automatisch einen Index haben, aber die Abfrage kennzeichnet sie weiterhin.
Debasish Mitra
21

Ja - für Primärschlüssel, nein - für Fremdschlüssel (mehr in den Dokumenten ).

\d <table_name>

in "psql" zeigt eine Beschreibung einer Tabelle einschließlich aller ihrer Indizes.

Milen A. Radev
quelle
11
Als Referenz listet \ di auch alle Indizes in der Datenbank auf.
Daemin
14

Ich finde es toll, wie dies im Artikel Coole Leistungsmerkmale von EclipseLink 2.5 erklärt wird

Indizierung von Fremdschlüsseln

Die erste Funktion ist die automatische Indizierung von Fremdschlüsseln. Die meisten Leute gehen fälschlicherweise davon aus, dass Datenbanken standardmäßig Fremdschlüssel indizieren. Nun, das tun sie nicht. Primärschlüssel werden automatisch indiziert, Fremdschlüssel jedoch nicht. Dies bedeutet, dass jede auf dem Fremdschlüssel basierende Abfrage vollständige Tabellenscans ausführt. Dies sind alle OneToMany- , ManyToMany- oder ElementCollection- Beziehungen sowie viele OneToOne- Beziehungen und die meisten Abfragen zu Beziehungen, die Verknüpfungen oder Objektvergleiche beinhalten . Dies kann ein großes Leistungsproblem sein, und Sie sollten Ihre Fremdschlüsselfelder immer indizieren.

Nabi
quelle
5
Wenn wir unsere Fremdschlüsselfelder immer indizieren sollten , warum tun die Datenbankmodule das nicht bereits? Es scheint mir, dass dies mehr ist, als man denkt.
Bobort
3
@Bobort Da das Hinzufügen eines Index zu Leistungseinbußen bei allen Einfügungen, Aktualisierungen und Löschungen führt, können sich in diesem Fall viele Fremdschlüssel wirklich summieren. Aus diesem Grund ist dieses Verhalten meiner Meinung nach opt-in - Entwickler sollten in dieser Angelegenheit eine bewusste Entscheidung treffen. Es kann auch Fälle geben, in denen Fremdschlüssel zur Durchsetzung der Datenintegrität verwendet werden, aber nicht häufig oder überhaupt nicht abgefragt werden - in diesem Fall wäre die Leistungsstrafe des Index umsonst
Dr.Strangelove
3
Es gibt auch schwierige Fälle mit zusammengesetzten Indizes, da diese von links nach rechts angewendet werden: dh der zusammengesetzte Index für [Benutzer_ID, Artikel_ID] in der Kommentartabelle würde effektiv sowohl das Abfragen ALLER Kommentare durch den Benutzer (z. B. das Anzeigen eines aggregierten Kommentarprotokolls auf der Website) als auch das Abrufen aller abdecken Kommentare dieses Benutzers zu einem bestimmten Artikel. Das Hinzufügen eines separaten Index für user_id ist in diesem Fall eine Verschwendung von Speicherplatz und CPU-Zeit beim Einfügen / Aktualisieren / Löschen.
Dr. Strangelove
2
Aha! Dann ist der Rat schlecht! Wir sollten unsere Fremdschlüssel NICHT immer indizieren. Wie @ Dr.Strangelove betont hat, gibt es tatsächlich Zeiten, in denen wir sie nicht indizieren wollen! Vielen Dank, Dr.!
Bobort
Warum werden sie nicht standardmäßig indiziert? Gibt es einen wichtigen Anwendungsfall, der dies erforderlich macht?
Adam Arold
7

Für a PRIMARY KEYwird ein Index mit der folgenden Meldung erstellt:

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

Für ein FOREIGN KEY, wird die Einschränkung erstellt werden , wenn kein Index für die referenc ist ed Tabelle.

Ein Index für referenc ing - Tabelle ist nicht erforderlich (obwohl gewünscht), und wird daher nicht implizit erstellt werden.

Quassnoi
quelle