Postgresy i indeksy dotyczące kluczy obcych i kluczy podstawowych

343

Czy Postgres automatycznie umieszcza indeksy na kluczach obcych i kluczach podstawowych? Jak mogę powiedzieć? Czy istnieje polecenie, które zwróci wszystkie indeksy w tabeli?

mainstringargs
źródło

Odpowiedzi:

406

PostgreSQL automatycznie tworzy indeksy na kluczach podstawowych i unikalnych ograniczeniach, ale nie na stronie odniesienia relacji klucza obcego.

Kiedy Pg tworzy domyślny indeks, wyemituje NOTICEkomunikat poziomu, który można zobaczyć w psqllogach systemowych i / lub w dziennikach systemowych, aby można było zobaczyć, kiedy to nastąpi. Automatycznie tworzone indeksy są również widoczne w \ddanych wyjściowych dla tabeli.

Dokumentacja unikalnych indeksów mówi:

PostgreSQL automatycznie tworzy indeks dla każdego ograniczenia unikalnego i kluczowego, aby wymusić unikalność. Dlatego nie jest konieczne jawne tworzenie indeksu dla kolumn klucza podstawowego.

a dokumentacja ograniczeń mówi:

Ponieważ USUWANIE wiersza z tabeli, do której następuje odwołanie, lub AKTUALIZACJA kolumny, do której następuje odwołanie, będzie wymagało skanowania tabeli odniesienia w celu znalezienia wierszy pasujących do starej wartości, często dobrym pomysłem jest zaindeksowanie kolumn odniesienia. Ponieważ nie zawsze jest to potrzebne i istnieje wiele możliwości wyboru sposobu indeksowania, deklaracja ograniczenia klucza obcego nie tworzy automatycznie indeksu w kolumnach odniesienia.

Dlatego jeśli chcesz, musisz samodzielnie tworzyć indeksy kluczy obcych.

Zauważ, że jeśli użyjesz podstawowych kluczy obcych, takich jak 2 FK jako PK w tabeli od M do N, będziesz mieć indeks na PK i prawdopodobnie nie będziesz musiał tworzyć żadnych dodatkowych indeksów.

Chociaż zwykle dobrym pomysłem jest utworzenie indeksu w kolumnach kluczy obcych po stronie odniesienia (lub w tym), nie jest to wymagane. Każdy indeks dodać spowalnia operacje DML lekko w dół, więc płacisz koszt wykonania na każdym INSERT, UPDATElub DELETE. Jeśli indeks jest rzadko używany, nie warto go mieć.

Philipp
źródło
26
Mam nadzieję, że ta edycja jest OK; Dodałem linki do odpowiedniej dokumentacji, cytat, który wyraźnie mówi, że strona odniesienia relacji FK nie tworzy niejawnego indeksu, pokazał, jak zobaczyć indeksy w psql, przeformułował pierwszy par dla jasności i dodał zwróć uwagę, że indeksy nie są darmowe, więc ich dodawanie nie zawsze jest właściwe.
Craig Ringer
1
@CraigRinger, jak ustalić, czy korzyść z indeksu przewyższa jego koszt? Czy profiluję testy jednostkowe przed / po dodaniu indeksu i sprawdzam ogólny wzrost wydajności? Czy jest jakiś lepszy sposób?
Gili
2
@Gili To temat na osobne pytanie dba.stackexchange.com.
Craig Ringer
34

Jeśli chcesz wyświetlić listę indeksów wszystkich tabel w schemacie (ach) z twojego programu, wszystkie informacje są dostępne w katalogu:

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

Jeśli chcesz zagłębić się dalej (takie jak kolumny i kolejność), musisz spojrzeć na pg_catalog.pg_index. Korzystanie psql -E [dbname]przydaje na zastanawianie się, jak kwerendy katalogu.

dlandia
źródło
4
+1, ponieważ użycie pg_catalog i psql -E jest naprawdę bardzo przydatne
Ghislain Leveque
„W celach informacyjnych \diwyświetli również wszystkie indeksy w bazie danych”. (komentarz skopiowany z innej odpowiedzi, dotyczy również tutaj)
Risadinha
33

To zapytanie wyświetli brakujące indeksy kluczy obcych , oryginalne źródło .

Edycja : Pamiętaj, że nie sprawdzi małych tabel (mniej niż 9 MB) i niektórych innych przypadków. Zobacz końcowe WHEREoświadczenie.

-- 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
źródło
7
Nie wydaje się działać. Zwraca 0 wierszy, gdy wiem, że mam kolumny bez indeksów, które odwołują się do tabel domen.
juanitogan
6
@juanitogan Uważaj na whereklauzule: Między innymi bierze pod uwagę tylko tabele, których rozmiar jest większy niż 9 MB.
Matthias
@Matthias - Ach, rozumiem. Dzięki. Tak, oczywiście nie poświęciłem czasu na przeczytanie kodu. Nie było to wystarczająco krytyczne, aby niepokoić. OP mógł wspomnieć o ograniczeniach. Może kiedyś to sprawdzę.
juanitogan
@SergeyB wydaje się dawać fałszywy wynik pozytywny w odniesieniu do kolumn, do których istnieją odniesienia z ograniczeniem klucza podstawowego, a zatem automatycznie ma indeks, ale zapytanie wciąż je oznacza.
Debasish Mitra
21

Tak - dla kluczy podstawowych, nie - dla kluczy obcych (więcej w dokumentacji ).

\d <table_name>

w „psql” pokazuje opis tabeli zawierający wszystkie jej indeksy.

Milen A. Radev
źródło
11
Dla odniesienia \ di wyświetli również wszystkie indeksy w bazie danych.
Daemin,
14

Uwielbiam to, jak to wyjaśniono w artykule Fajne funkcje wydajnościowe EclipseLink 2.5

Indeksowanie kluczy obcych

Pierwszą funkcją jest automatyczne indeksowanie kluczy obcych. Większość osób błędnie zakłada, że ​​bazy danych domyślnie indeksują klucze obce. Cóż, oni nie. Klucze podstawowe są automatycznie indeksowane, ale klucze obce nie. Oznacza to, że każde zapytanie oparte na kluczu obcym będzie wykonywać pełne skanowanie tabeli. Jest to dowolna relacja OneToMany , ManyToMany lub ElementCollection , a także wiele relacji OneToOne i większość zapytań dotyczących dowolnej relacji obejmującej połączenia lub porównania obiektów . Może to być poważny problem z wydajnością i zawsze należy indeksować pola kluczy obcych.

Nabi
źródło
5
Jeśli zawsze powinniśmy indeksować pola kluczy obcych, dlaczego silniki baz danych już tego nie robią? Wydaje mi się, że jest w tym coś więcej niż na pierwszy rzut oka.
Bobort
3
@Bobort Ponieważ dodanie indeksu powoduje obniżenie wydajności we wszystkich wstawkach, aktualizacjach i usunięciach, a w tym przypadku naprawdę wiele kluczy obcych może się sumować. Właśnie dlatego takie zachowanie jest opcjonalne - programista powinien dokonać świadomego wyboru w tej sprawie. Mogą również zdarzyć się przypadki użycia klucza obcego w celu wymuszenia integralności danych, ale nie jest on często pytany lub w ogóle pytany - w tym przypadku kara za wykonanie indeksu byłaby na nic
Dr.Strangelove
3
Istnieją również trudne przypadki z indeksami złożonymi, ponieważ są one stosowane od lewej do prawej: tzn. Indeks złożony w [identyfikator_użytkownika, identyfikator_wydawcy] w tabeli komentarzy skutecznie obejmowałby zarówno zapytania WSZYSTKICH komentarzy użytkownika (np. Aby wyświetlić łączny dziennik komentarzy na stronie internetowej) i pobranie wszystkich komentarze zgłoszone przez tego użytkownika do określonego artykułu. Dodanie osobnego indeksu do user_id w tym przypadku skutecznie marnuje miejsce na dysku i czas procesora na wstawianie / aktualizacje / usuwanie.
Dr.Strangelove,
2
Aha! Zatem rada jest słaba! NIE powinniśmy zawsze indeksować naszych kluczy obcych. Jak zauważył @ Dr.Strangelove, są chwile, kiedy nie chcemy ich indeksować! Dziękuję bardzo, Dr.
Bobort,
Dlaczego nie są one domyślnie indeksowane? Czy istnieje ważny przypadek użycia, który powoduje, że jest to konieczne?
Adam Arold,
7

Dla a PRIMARY KEYzostanie utworzony indeks z następującym komunikatem:

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

Dla FOREIGN KEY, ograniczenie nie zostanie utworzony, jeśli nie ma indeks na referenc ed tabeli.

Indeks na referenc ing tabeli nie jest wymagane (choć konieczne), a zatem nie będzie domyślnie tworzony.

Quassnoi
źródło