Postgres và Index trên Khóa ngoài và Khóa chính


342

Postgres có tự động đặt chỉ mục vào Khóa ngoài và Khóa chính không? Làm thế nào tôi có thể nói? Có một lệnh sẽ trả về tất cả các chỉ mục trên một bảng?

Câu trả lời:


405

PostgreSQL tự động tạo các chỉ mục trên các khóa chính và các ràng buộc duy nhất, nhưng không phải ở phía tham chiếu của các mối quan hệ khóa ngoài.

Khi PG tạo một chỉ mục ngầm, nó sẽ phát ra một NOTICEthông báo -level mà bạn có thể thấy trong psqlvà / hoặc nhật ký hệ thống, do đó bạn có thể thấy khi nào nó xảy ra. Các chỉ mục được tạo tự động cũng hiển thị trong \dđầu ra cho một bảng.

Các tài liệu về các chỉ mục duy nhất cho biết:

PostgreSQL tự động tạo một chỉ mục cho từng ràng buộc duy nhất và ràng buộc khóa chính để thực thi tính duy nhất. Vì vậy, không cần thiết phải tạo một chỉ mục rõ ràng cho các cột khóa chính.

và các tài liệu về các ràng buộc nói:

Do XÓA một hàng từ bảng được tham chiếu hoặc CẬP NHẬT của cột được tham chiếu sẽ yêu cầu quét bảng tham chiếu cho các hàng khớp với giá trị cũ, nên thường lập chỉ mục các cột tham chiếu. Bởi vì điều này không phải lúc nào cũng cần thiết và có nhiều lựa chọn về cách lập chỉ mục, khai báo ràng buộc khóa ngoại không tự động tạo chỉ mục trên các cột tham chiếu.

Do đó, bạn phải tự tạo các chỉ mục trên khóa ngoài nếu bạn muốn chúng.

Lưu ý rằng nếu bạn sử dụng khóa chính ngoại lai, như 2 FK làm PK trong bảng M-to-N, bạn sẽ có một chỉ mục trên PK và có thể không cần tạo thêm bất kỳ chỉ mục nào.

Mặc dù thường là một ý tưởng tốt để tạo một chỉ mục trên (hoặc bao gồm) các cột khóa ngoại bên tham chiếu của bạn, nhưng không bắt buộc. Mỗi chỉ mục bạn thêm làm chậm các hoạt động DML xuống một chút, do đó bạn phải trả chi phí hiệu năng cho mỗi INSERT, UPDATEhoặc DELETE. Nếu chỉ số hiếm khi được sử dụng, nó có thể không có giá trị.


26
Tôi hy vọng chỉnh sửa này là OK; Tôi đã thêm các liên kết đến tài liệu có liên quan, một trích dẫn làm cho nó hoàn toàn rõ ràng rằng phía tham chiếu của các mối quan hệ FK không tạo ra một chỉ mục ngầm, chỉ ra cách xem các chỉ mục trong psql, viết lại mệnh đề thứ nhất cho rõ ràng và thêm vào lưu ý rằng các chỉ mục không miễn phí nên không phải lúc nào cũng đúng.
Craig Ringer

1
@CraigRinger, làm thế nào để bạn xác định xem lợi ích của một chỉ mục có vượt quá chi phí của nó không? Tôi có hồ sơ kiểm tra đơn vị trước / sau khi thêm chỉ mục và kiểm tra mức tăng hiệu suất tổng thể không? đây có phải là cách tốt hơn không?
Gili

2
@Gili Đó là một chủ đề cho một câu hỏi dba.stackexchange.com riêng biệt.
Craig Ringer

34

Nếu bạn muốn liệt kê các chỉ mục của tất cả các bảng trong (các) lược đồ của bạn từ chương trình của bạn, tất cả thông tin đều có trong danh mục:

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

Nếu bạn muốn đi sâu hơn (như cột và đặt hàng), bạn cần xem pg_catalog.pg_index. Sử dụng psql -E [dbname]có ích để tìm ra cách truy vấn danh mục.


4
+1 vì việc sử dụng pg_catalog và psql -E thực sự rất hữu ích
Ghislain Leveque

"Để tham khảo \dicũng sẽ liệt kê tất cả các chỉ mục trong cơ sở dữ liệu." (bình luận sao chép mẫu câu trả lời khác, cũng áp dụng ở đây)
Risadinha

33

Truy vấn này sẽ liệt kê các chỉ mục bị thiếu trên các khóa ngoại , nguồn gốc .

Chỉnh sửa : Lưu ý rằng nó sẽ không kiểm tra các bảng nhỏ (ít hơn 9 MB) và một số trường hợp khác. Xem WHEREtuyên bố cuối cùng .

-- 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
Không xuất hiện để làm việc. Trả về 0 hàng khi tôi biết tôi có các cột không có chỉ mục trên chúng mà bảng miền tham chiếu.
juanitogan

6
@juanitogan Xem các wheremệnh đề: Trong số các mệnh đề khác, chỉ cần xem xét các bảng có kích thước lớn hơn 9 MB.
Matthias

@Matthias - À, hiểu rồi. Cảm ơn. Vâng, tôi rõ ràng đã không mất thời gian để đọc qua mã. Nó không đủ quan trọng để làm phiền. OP có thể đã đề cập đến những hạn chế. Có lẽ đôi khi tôi sẽ kiểm tra lại.
juanitogan

@SergeyB dường như cung cấp dương tính giả trên các cột được tham chiếu có ràng buộc khóa chính đối với chúng, do đó tự động có một chỉ mục nhưng truy vấn vẫn gắn cờ chúng.
Debraish Mitra 22/03/18

21

Có - đối với khóa chính, không - đối với khóa ngoại (nhiều hơn trong tài liệu ).

\d <table_name>

trong "psql" hiển thị mô tả về bảng bao gồm tất cả các chỉ mục của nó.


11
Để tham khảo \ di cũng sẽ liệt kê tất cả các chỉ mục trong cơ sở dữ liệu.
Đà Nẵng

14

Tôi thích cách giải thích điều này trong bài viết Các tính năng hiệu suất tuyệt vời của EclipseLink 2.5

Lập chỉ mục khóa ngoại

Tính năng đầu tiên là tự động lập chỉ mục các khóa ngoại. Hầu hết mọi người đều cho rằng cơ sở dữ liệu lập chỉ mục các khóa ngoại theo mặc định. Chà, họ thì không. Khóa chính được lập chỉ mục tự động, nhưng khóa ngoại thì không. Điều này có nghĩa là bất kỳ truy vấn nào dựa trên khóa ngoại sẽ được thực hiện quét toàn bộ bảng. Đây là bất kỳ OneToMany , ManyToMany hoặc ElementCollection mối quan hệ, cũng như nhiều OneToOne mối quan hệ, và hầu hết các truy vấn trên bất kỳ mối quan hệ liên quan đến tham gia hoặc so sánh đối tượng . Đây có thể là một vấn đề thực hiện chính và bạn phải luôn lập chỉ mục các trường khóa ngoại của mình.


5
Nếu chúng ta phải luôn lập chỉ mục các trường khóa ngoại, tại sao các công cụ cơ sở dữ liệu không làm điều đó? Dường như với tôi có nhiều thứ hơn là bắt mắt.
Bobort

3
@Bobort Vì việc thêm chỉ mục phải chịu hình phạt hiệu suất đối với tất cả các lần chèn, cập nhật và xóa và rất nhiều khóa ngoại có thể thực sự bổ sung trong trường hợp này. Đó là lý do tại sao hành vi này được chọn tham gia - tôi đoán nhà phát triển nên đưa ra lựa chọn có ý thức trong vấn đề này. Cũng có thể có trường hợp khi khóa ngoại được sử dụng để thực thi tính toàn vẹn dữ liệu, nhưng không được truy vấn thường xuyên hoặc truy vấn gì cả - trong trường hợp này, hình phạt về hiệu suất của chỉ số sẽ không có gì
Dr.Strangelove

3
Ngoài ra còn có các trường hợp khó khăn với các chỉ số ghép, vì các trường hợp này được áp dụng từ trái sang phải: tức là chỉ số ghép trên [user_id, article_id] trên bảng nhận xét sẽ bao gồm cả truy vấn TẤT CẢ bình luận của người dùng (ví dụ: để hiển thị nhật ký tổng hợp trên trang web) và tìm nạp tất cả ý kiến ​​của người dùng này cho một bài viết cụ thể. Thêm một chỉ mục riêng trên user_id trong trường hợp này thực sự gây lãng phí không gian đĩa và thời gian cpu khi chèn / cập nhật / xóa.
Dr.Strangelove

2
Aha! Vậy thì lời khuyên còn kém! Chúng ta KHÔNG nên luôn luôn lập chỉ mục các khóa ngoại của chúng tôi. Như @ Dr.Strangelove đã chỉ ra, thực sự có những lúc chúng ta không muốn lập chỉ mục cho họ! Cảm ơn bạn rất nhiều, Tiến sĩ!
Bobort

Tại sao họ không được lập chỉ mục theo mặc định? Có một trường hợp sử dụng quan trọng mà làm cho điều này cần thiết?
Adam Arold

7

Đối với a PRIMARY KEY, một chỉ mục sẽ được tạo với thông báo sau:

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

Đối với một FOREIGN KEY, hạn chế sẽ không được tạo nếu không có chỉ số trên referenc ed bảng.

Một chỉ mục trên referenc ing bảng không được yêu cầu (mặc dù mong muốn), và do đó sẽ không được tạo ngầm.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.