Postgres dan Indeks pada Kunci Asing dan Kunci Utama

343

Apakah Postgres secara otomatis menempatkan indeks pada Kunci Asing dan Kunci Utama? Bagaimana saya tahu? Apakah ada perintah yang akan mengembalikan semua indeks di atas meja?

mainstringargs
sumber

Jawaban:

406

PostgreSQL secara otomatis membuat indeks pada kunci primer dan batasan unik, tetapi tidak pada sisi referensi dari hubungan kunci asing.

Ketika Pg membuat indeks implisit, ia akan memancarkan NOTICEpesan -level yang dapat Anda lihat di psqldan / atau sistem log, sehingga Anda bisa melihat kapan itu terjadi. Indeks yang dibuat secara otomatis juga terlihat dalam \doutput untuk tabel.

The dokumentasi di indeks unik mengatakan:

PostgreSQL secara otomatis membuat indeks untuk setiap batasan unik dan batasan kunci utama untuk menegakkan keunikan. Dengan demikian, tidak perlu membuat indeks secara eksplisit untuk kolom kunci utama.

dan dokumentasi tentang kendala mengatakan:

Karena HAPUS baris dari tabel yang direferensikan atau PEMBARUAN dari kolom yang direferensikan akan memerlukan pemindaian tabel referensi untuk baris yang cocok dengan nilai lama, sering kali merupakan ide yang baik untuk mengindeks kolom referensi. Karena ini tidak selalu diperlukan, dan ada banyak pilihan yang tersedia tentang cara mengindeks, deklarasi batasan kunci asing tidak secara otomatis membuat indeks pada kolom referensi.

Karena itu Anda harus membuat sendiri indeks pada kunci asing jika Anda menginginkannya.

Perhatikan bahwa jika Anda menggunakan kunci asing-kunci, seperti 2 FK sebagai PK dalam tabel M-to-N, Anda akan memiliki indeks pada PK dan mungkin tidak perlu membuat indeks tambahan.

Meskipun biasanya merupakan ide yang baik untuk membuat indeks pada (atau termasuk) kolom kunci asing di sisi referensi Anda, itu tidak diperlukan. Setiap indeks yang Anda tambahkan memperlambat operasi DML sedikit, sehingga Anda membayar biaya kinerja pada setiap INSERT, UPDATEatau DELETE. Jika indeks jarang digunakan, mungkin tidak layak dimiliki.

Philipp
sumber
26
Saya harap hasil edit ini OK; Saya telah menambahkan tautan ke dokumentasi yang relevan, kutipan yang membuatnya sangat jelas bahwa sisi referensi dari hubungan FK tidak menghasilkan indeks implisit, menunjukkan cara melihat indeks dalam psql, mengulangi par pertama untuk kejelasan, dan menambahkan perhatikan bahwa indeks tidak gratis sehingga tidak selalu benar untuk menambahkannya.
Craig Ringer
1
@CraigRinger, bagaimana Anda menentukan apakah manfaat indeks melebihi biayanya? Apakah saya menguji unit profil sebelum / setelah menambahkan indeks dan memeriksa untuk kenaikan kinerja secara keseluruhan? Atau ada cara yang lebih baik?
Gili
2
@Gili Itu topik untuk pertanyaan dba.stackexchange.com terpisah.
Craig Ringer
34

Jika Anda ingin mendaftar indeks semua tabel dalam skema Anda dari program Anda, semua informasi ada di 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

Jika Anda ingin mempelajari lebih lanjut (seperti kolom dan pemesanan), Anda perlu melihat pg_catalog.pg_index. Menggunakan psql -E [dbname]sangat berguna untuk mencari tahu cara menanyakan katalog.

dland
sumber
4
+1 karena penggunaan pg_catalog dan psql -E benar-benar sangat berguna
Ghislain Leveque
"Untuk referensi \dijuga akan mencantumkan semua indeks dalam database." (komentar disalin dari jawaban lain, berlaku di sini juga)
Risadinha
33

Kueri ini akan mencantumkan indeks yang hilang pada kunci asing , sumber asli .

Sunting : Perhatikan bahwa ini tidak akan memeriksa tabel kecil (kurang dari 9 MB) dan beberapa kasus lainnya. Lihat WHEREpernyataan akhir .

-- 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
sumber
7
Tampaknya tidak berfungsi. Mengembalikan 0 baris ketika saya tahu saya memiliki kolom tanpa indeks pada mereka yang merujuk tabel domain.
juanitogan
6
@juanitogan Perhatikan whereklausa: Di antara yang lain, hanya perlu mempertimbangkan tabel yang ukurannya lebih dari 9 MB.
Matthias
@Matithias - Ah, mengerti. Terima kasih. Ya, saya jelas tidak mengambil waktu untuk membaca kode. Itu tidak cukup kritis untuk mengganggu. OP bisa menyebutkan keterbatasan. Mungkin saya akan memeriksanya lagi kapan-kapan.
juanitogan
@SergeyB tampaknya memberikan false positive pada kolom yang direferensikan yang memiliki batasan kunci primer pada mereka, sehingga secara otomatis memiliki indeks tetapi permintaan masih menandai mereka.
Debasish Mitra
21

Ya - untuk kunci utama, tidak - untuk kunci asing (lebih banyak di dokumen ).

\d <table_name>

di "psql" menunjukkan deskripsi tabel termasuk semua indeksnya.

Milen A. Radev
sumber
11
Untuk referensi \ di juga akan mencantumkan semua indeks dalam database.
Daemin
14

Saya suka bagaimana ini dijelaskan dalam artikel fitur kinerja Keren EclipseLink 2.5

Pengindeksan Kunci Asing

Fitur pertama adalah pengindeksan otomatis kunci asing. Kebanyakan orang salah berasumsi bahwa basis data indeks kunci asing secara default. Yah, mereka tidak. Kunci primer diindeks secara otomatis, tetapi kunci asing tidak. Ini berarti setiap permintaan yang didasarkan pada kunci asing akan melakukan pemindaian tabel penuh. Ini adalah hubungan OneToMany , ManyToMany atau ElementCollection , serta banyak hubungan OneToOne , dan sebagian besar kueri tentang hubungan apa pun yang melibatkan gabungan atau perbandingan objek . Ini bisa menjadi masalah kinerja utama, dan Anda harus selalu mengindeks bidang kunci asing Anda.

Nabi
sumber
5
Jika kita harus selalu mengindeks bidang kunci asing kita, mengapa mesin database tidak melakukan itu? Sepertinya saya ada lebih dari ini memenuhi mata.
Bobort
3
@Bobort Karena menambahkan indeks akan dikenakan penalti kinerja pada semua sisipan, pembaruan, dan penghapusan, dan banyak kunci asing dapat benar-benar bertambah dalam kasus ini. Itu sebabnya perilaku ini adalah opt-in kurasa - pengembang harus membuat pilihan sadar dalam hal ini. Mungkin juga ada kasus ketika kunci asing digunakan untuk menegakkan integritas data, tetapi tidak sering ditanya atau ditanya sama sekali - dalam hal ini hukuman kinerja indeks tidak akan ada
gunanya
3
Ada juga kasus rumit dengan indeks majemuk, karena diterapkan dari kiri ke kanan: yaitu indeks gabungan pada [user_id, article_id] pada tabel komentar akan secara efektif mencakup kedua kueri SEMUA komentar oleh pengguna (misalnya untuk menampilkan komentar komentar teragregasi di situs web) dan mengambil semua komentar yang dibuat oleh pengguna ini untuk artikel tertentu. Menambahkan indeks terpisah pada user_id dalam hal ini secara efektif membuang ruang disk dan waktu cpu pada sisipan / pembaruan / penghapusan.
Dr.Strangelove
2
Aha! Maka sarannya buruk! Kita TIDAK selalu harus mengindeks kunci asing kita. Seperti yang ditunjukkan oleh Dr.Strangelove, sebenarnya ada saat-saat ketika kita tidak ingin mengindeksnya! Terima kasih banyak, Dr.
Bobort
Mengapa mereka tidak diindeks secara default? Apakah ada use case penting yang membuat ini perlu?
Adam Arold
7

Untuk a PRIMARY KEY, indeks akan dibuat dengan pesan berikut:

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

Untuk FOREIGN KEY, kendala tidak akan dibuat jika tidak ada indeks pada referenc ed meja.

Indeks pada referenc ing meja tidak diperlukan (meskipun diinginkan), dan karena itu tidak akan secara implisit dibuat.

Quassnoi
sumber