Индексирование иностранных ключей в Postgresql - PullRequest
2 голосов
/ 22 марта 2020

Как и многие Postgres n00bs, у нас есть много таблиц с ограничениями внешнего ключа, которые не индексируются. В некоторых случаях это не должно сильно повлиять на производительность, но это будет предметом дальнейшего анализа.

Я прочитал следующую статью: https://www.cybertec-postgresql.com/en/index-your-foreign-key/

И использовал следующий запрос, чтобы найти все внешние ключи без индекса:

SELECT c.conrelid::regclass AS "table",
       /* list of key column names in order */
       string_agg(a.attname, ',' ORDER BY x.n) AS columns,
       pg_catalog.pg_size_pretty(
          pg_catalog.pg_relation_size(c.conrelid)
       ) AS size,
       c.conname AS constraint,
       c.confrelid::regclass AS referenced_table
FROM pg_catalog.pg_constraint c
   /* enumerated key column numbers per foreign key */
   CROSS JOIN LATERAL
      unnest(c.conkey) WITH ORDINALITY AS x(attnum, n)
   /* name for each key column */
   JOIN pg_catalog.pg_attribute a
      ON a.attnum = x.attnum
         AND a.attrelid = c.conrelid
WHERE NOT EXISTS
        /* is there a matching index for the constraint? */
        (SELECT 1 FROM pg_catalog.pg_index i
         WHERE i.indrelid = c.conrelid
           /* the first index columns must be the same as the
              key columns, but order doesn't matter */
           AND (i.indkey::smallint[])[0:cardinality(c.conkey)-1]
               @> c.conkey::int[])
  AND c.contype = 'f'
GROUP BY c.conrelid, c.conname, c.confrelid
ORDER BY pg_catalog.pg_relation_size(c.conrelid) DESC;

Это показывает мне для таблиц с составными уникальными ограничениями только «один» из столбцов уникального индекса:

\d topics_items;
-----------------+---------+--------------+---------------+------------------------------
 topics_items_id | integer |              | not null      | generated always as identity
 topic_id        | integer |              | not null      |
 item_id         | integer |              | not null      |
Index:
    "topics_items_pkey" PRIMARY KEY, btree (topics_items_id)
    "topic_id_item_id_unique" UNIQUE CONSTRAINT, btree (topic_id, item_id)
Foreign Keys:
    "topics_items_item_id_fkey" FOREIGN KEY (item_id) REFERENCES items(item_id) ON DELETE CASCADE
    "topics_items_topic_id_fkey" FOREIGN KEY (topic_id) REFERENCES topics(topic_id) ON DELETE CASCADE

В этом случае контрольный запрос находит только item_id, а не topic_id как неиндексированное поле.

Справедливо ли сказать, что это просто проблема используемого запроса, и Я должен отдельно проиндексировать оба поля (topic_id и item_id) - или там какое-то черное колдовство, и только для item_id нужен индекс?

Ответы [ 2 ]

3 голосов
/ 22 марта 2020

tl; др Вам необходимо добавить индекс на item_id. «Черные маги c» индексации Postgres описаны в 11. Индексы .

У вас есть составной индекс на (topic_id, item_id), и порядок столбцов важен. Postgres может использовать это для индексации запросов на topic_id, запросов на topic_id и item_id, но не (или менее эффективно) item_id в одиночку.

С 11.3. Индексы с несколькими столбцами ...

Индекс B-дерева с несколькими столбцами можно использовать с условиями запроса, которые включают любое подмножество столбцов индекса, но индекс наиболее эффективен, когда существуют ограничения на ведущие (крайние левые) столбцы.

-- indexed
select *
from topics_items
where topic_id = ?

-- also indexed
select *
from topics_items
where topic_id = ?
  and item_id = ?

-- probably not indexed
select *
from topics_items
where item_id = ?

Это связано с тем, что в составном индексе, например (topic_id, item_id), сначала хранится идентификатор topi c, а затем идентификаторы элементов, которые также имеют этот идентификатор topi c. , Чтобы эффективно найти идентификатор элемента в этом индексе, Postgres должен сначала сузить поиск с помощью идентификатора topi c.


Postgres может отменить Индекс, если он думает, что стоит усилий. Если имеется небольшое количество возможных идентификаторов topi c и большое количество возможных идентификаторов индекса, он будет искать идентификатор индекса в каждом topi c ID.

Например, предположим, что у вас есть 10 возможных идентификаторов topi c и 1000 возможных идентификаторов предметов и ваш индекс (topic_id, index_id). Это похоже на наличие 10 ячеек с идентифицированным топи c ID, каждый из которых содержит 1000 ячеек с идентифицированным идентификатором внутри. Чтобы попасть в корзины с идентификатором предмета, он должен заглянуть внутрь каждой корзины с идентификатором topi c. Чтобы использовать этот индекс для where item_id = 23 Postgres, необходимо выполнить поиск в каждом из 10 сегментов topi c ID для всех сегментов с идентификатором элемента 23.

Но если у вас есть 1000 возможных идентификаторов topi c и 10 возможных идентификаторов элементов, Postgres придется искать в 1000 корзинах topi c идентификаторов. Скорее всего, вместо этого будет выполнено полное сканирование таблицы. В этом случае вы захотите обратить свой индекс обратно и сделать его (item_id, topic_id).

Это в значительной степени зависит от наличия хорошей табличной статистики, что означает, что нужно убедиться, что автоочистка работает правильно.

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


Postgres также может использовать множественные индексы, если считает, что запрос будет выполнен быстрее . Например, если у вас был индекс на topic_id и индекс на item_id, он может использовать оба индекса и объединить результаты. Например, where topic_id = 23 or item_id = 42 может использовать индекс topic_id для поиска topi c ID 23 и индекс item_id для поиска идентификатора элемента 42, а затем объединить результаты.

Это обычно медленнее, чем составной (topic_id, item_id) индекс. Это также может быть медленнее, чем использование одного индекса, поэтому не удивляйтесь, если Postgres решит не использовать несколько индексов.


В общем, для индексов b-дерева, когда у вас есть два В столбцах у вас есть три возможных комбинации.

  • a + b
  • a
  • b

И вам нужно два индекса.

  • (a, b) - a и a + b
  • (b) - b

(a, b) охватывает оба поиска a и a + б. (b) охватывает поиск b.

Если у вас есть три столбца, у вас есть семь возможных комбинаций.

  • a + b + c
  • a + b
  • a + c
  • a
  • b + c
  • b
  • c

Но вам нужны только три индекса.

  • (a, b, c) - a, a + b, a + b + c
  • (б, c) - б, б + c
  • (c, а) - c, c + а

Однако вы, вероятно, на самом деле хотите избежать индекса на три столбца. Это часто медленнее . То, что вы на самом деле хотите, это:

  • (а, б)
  • (б, c)
  • (c, а)

Многоколоночные индексы следует использовать с осторожностью. В большинстве случаев достаточно индекса для одного столбца, который экономит пространство и время. Индексы с более чем тремя столбцами вряд ли будут полезны, если использование таблицы не будет чрезвычайно стилизовано.

Чтение из индекса происходит медленнее, чем чтение из таблицы. Вы хотите, чтобы ваши индексы уменьшали количество строк, которые должны быть прочитаны, но вы не хотите, чтобы Postgres выполнял сканирование индекса больше, чем необходимо.

Ограничения на столбцы справа ... отмечены в индексе, поэтому они сохраняют посещения самой таблицы, но не уменьшают ту часть индекса, которая должна быть отсканирована. Например, если задан индекс для (a, b, c) и условие запроса WHERE a = 5 AND b> = 42 AND c <77, индекс должен быть отсканирован из первой записи с a = 5 и b = 42 до последней записи с a = 5. Записи индекса с c> = 77 будут пропущены, но их все равно придется сканировать.

1 голос
/ 23 марта 2020

Строки с определенным topic_id могут быть эффективно найдены с помощью индекса на (topic_id, item_id), поэтому мой запрос считает, что внешний ключ покрыт.

Индекс отсортирован на topic_id, и в пределах все записи с одинаковым topic_id, отсортировано по item_id. Это позволяет использовать его для поиска только по topic_id.

...