Postgres и индексы для внешних ключей и первичных ключей

344

Размещает ли Postgres автоматически индексы для внешних и первичных ключей? Как я могу сказать? Есть ли команда, которая будет возвращать все индексы в таблице?

mainstringargs
источник

Ответы:

406

PostgreSQL автоматически создает индексы для первичных ключей и уникальных ограничений, но не на ссылочной стороне отношений внешнего ключа.

Когда Pg создает неявный индекс, он NOTICEгенерирует сообщение -уровня, которое вы можете видеть в psqlи / или системных журналах, чтобы вы могли видеть, когда это происходит. Автоматически созданные индексы также отображаются в \dвыходных данных для таблицы.

Документации уникальных индексов говорит:

PostgreSQL автоматически создает индекс для каждого уникального ограничения и ограничения первичного ключа для обеспечения уникальности. Таким образом, нет необходимости явно создавать индекс для столбцов первичного ключа.

и документация по ограничениям гласит:

Поскольку для УДАЛЕНИЯ строки из ссылочной таблицы или ОБНОВЛЕНИЯ ссылочного столбца потребуется сканирование ссылочной таблицы на наличие строк, соответствующих старому значению, часто рекомендуется индексировать ссылочные столбцы. Поскольку это не всегда необходимо, и существует множество вариантов индексации, объявление ограничения внешнего ключа не создает автоматически индекс для ссылочных столбцов.

Поэтому вы должны сами создавать индексы для внешних ключей, если хотите.

Обратите внимание, что если вы используете первичные внешние ключи, такие как 2 FK, в качестве PK в таблице M-to-N, у вас будет индекс на PK, и, вероятно, вам не нужно создавать никаких дополнительных индексов.

Хотя обычно хорошей идеей является создание индекса для (или включающего) столбцов внешнего ключа ссылочной стороны, это не обязательно. Каждый индекс вы добавляете замедляет DML операции немного вниз, так что вы платите стоимость производительности на каждый INSERT, UPDATEили DELETE. Если индекс используется редко, он может не иметь смысла.

Philipp
источник
26
Я надеюсь, что это редактирование в порядке; Я добавил ссылки на соответствующую документацию, цитату, которая делает совершенно явным то, что ссылающаяся сторона отношений FK не производит неявный индекс, показал, как видеть индексы в psql, перефразировал 1-й параметр для ясности и добавил: обратите внимание, что индексы не являются бесплатными, поэтому добавлять их не всегда правильно.
Крейг Рингер
1
@CraigRinger, как вы определяете, превосходит ли преимущество индекса его стоимость? Нужно ли профилировать модульные тесты до / после добавления индекса и проверять общее повышение производительности? Или есть лучший способ?
Гили
2
@Gili Это тема для отдельного вопроса dba.stackexchange.com.
Крейг Рингер,
34

Если вы хотите перечислить индексы всех таблиц в ваших схемах из вашей программы, вся информация находится под рукой в ​​каталоге:

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

Если вы хотите углубиться (например, столбцы и порядок), вам нужно взглянуть на pg_catalog.pg_index. Использование psql -E [dbname]пригодится, чтобы выяснить, как запросить каталог.

dland
источник
4
+1, потому что использование pg_catalog и psql -E действительно очень полезно
Ghislain Leveque
«Для справки \diтакже приведу список всех индексов в базе данных». (комментарий , скопированная форма другого ответа, применяется и здесь)
Risadinha
33

В этом запросе будут перечислены отсутствующие индексы внешних ключей , первоисточник .

Изменить : Обратите внимание, что он не будет проверять небольшие таблицы (менее 9 МБ) и некоторые другие случаи. Смотрите окончательное 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
источник
7
Не похоже на работу. Возвращает 0 строк, когда я знаю, что у меня есть столбцы без индексов, которые ссылаются на таблицы доменов.
Хуанитоган
6
@juanitogan Следите за whereпредложениями. Помимо прочего, учитываются только таблицы, размер которых превышает 9 МБ.
Матиас
@Matthias - Ах, понял. Спасибо. Да, я явно не потратил время, чтобы прочитать код. Это не было достаточно критично, чтобы беспокоиться. ОП мог бы упомянуть ограничения. Может быть, я проверю это снова когда-нибудь.
Хуанитоган
@SergeyB, по-видимому, дает ложный положительный результат на столбцы, на которые ссылаются, с ограничением первичного ключа, таким образом, автоматически имея индекс, но запрос все равно помечает их.
Дебашиш Митра
21

Да - для первичных ключей, нет - для внешних ключей (подробнее в документации ).

\d <table_name>

в "psql" показано описание таблицы, включающей все ее индексы.

Милен А. Радев
источник
11
Для справки \ di также перечислит все индексы в базе данных.
Daemin
14

Мне нравится, как это объясняется в статье. Отличная производительность EclipseLink 2.5.

Индексирование иностранных ключей

Первая функция - автоматическая индексация внешних ключей. Большинство людей ошибочно полагают, что базы данных индексируют внешние ключи по умолчанию. Ну, они этого не делают. Первичные ключи автоматически индексируются, а внешние ключи - нет. Это означает, что любой запрос на основе внешнего ключа будет выполнять полное сканирование таблицы. Это любые отношения OneToMany , ManyToMany или ElementCollection , а также многие отношения OneToOne и большинство запросов по любым отношениям, включающим соединения или сравнения объектов . Это может быть серьезной проблемой производительности, и вы всегда должны индексировать поля внешних ключей.

Nabi
источник
5
Если мы всегда должны индексировать поля внешних ключей, почему движки баз данных уже не делают этого? Мне кажется, это нечто большее, чем кажется на первый взгляд.
Боборт
3
@Bobort Поскольку добавление индекса влечет за собой снижение производительности для всех вставок, обновлений и удалений, и в этом случае действительно может сложиться множество внешних ключей. Вот почему я думаю, что такое поведение является обязательным - разработчик должен сделать осознанный выбор в этом вопросе. Также могут быть случаи, когда внешний ключ используется для обеспечения целостности данных, но не запрашивается часто или вообще запрашивается - в этом случае
снижение
3
Также есть сложные случаи с составными индексами, поскольку они применяются слева направо: т. Е. Составной индекс для [user_id, article_id] в таблице комментариев будет эффективно охватывать как запросы ВСЕХ комментариев пользователя (например, для отображения агрегированного журнала комментариев на веб-сайте), так и получение всех комментарии, сделанные этим пользователем для конкретной статьи. Добавление отдельного индекса для user_id в этом случае фактически является пустой тратой дискового пространства и процессорного времени на вставках / обновлениях / удалениях.
Dr.Strangelove
2
Ага! Тогда совет плохой! Мы НЕ должны всегда индексировать наши внешние ключи. Как отметил @ Dr.Strangelove, бывают случаи, когда мы не хотим их индексировать! Большое вам спасибо, доктор!
Боборт
Почему они не индексируются по умолчанию? Есть ли важный вариант использования, который делает это необходимым?
Адам Арольд
7

Для a PRIMARY KEYбудет создан индекс со следующим сообщением:

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

Для FOREIGN KEY, ограничение не будет создан , если нет индекса на Ссылки на сайты эд таблицы.

Индекс Ссылки на сайты ИНГ таблицы не требуется (хотя желательно), и , следовательно , не будет создан неявно.

Quassnoi
источник