Может ли PostgreSQL индексировать столбцы массива?

151

Я не могу найти однозначного ответа на этот вопрос в документации. Если столбец имеет тип массива, будут ли все введенные значения индексироваться индивидуально?

Я создал простую таблицу с одним int[]столбцом и поместил на нее уникальный индекс. Я заметил, что не могу добавить тот же массив целых чисел, что наводит меня на мысль, что индекс является составной частью элементов массива, а не индексом каждого элемента.

INSERT INTO "Test"."Test" VALUES ('{10, 15, 20}');
INSERT INTO "Test"."Test" VALUES ('{10, 20, 30}');

SELECT * FROM "Test"."Test" WHERE 20 = ANY ("Column1");

Помогает ли индекс этому запросу?

IamIC
источник
Можно ли использовать тип данных jsonbи индексы? postgresql.org/docs/9.5/static/functions-json.html и postgresql.org/docs/9.5/static/datatype-json.html#JSON-INDEXING
user3791372 05

Ответы:

187

Да, вы можете индексировать массив, но вы должны использовать операторы массива и тип индекса GIN .

Пример:

    CREATE TABLE "Test"("Column1" int[]);
    INSERT INTO "Test" VALUES ('{10, 15, 20}');
    INSERT INTO "Test" VALUES ('{10, 20, 30}');

    CREATE INDEX idx_test on "Test" USING GIN ("Column1");

    -- To enforce index usage because we have only 2 records for this test... 
    SET enable_seqscan TO off;

    EXPLAIN ANALYZE
    SELECT * FROM "Test" WHERE "Column1" @> ARRAY[20];

Результат:

Bitmap Heap Scan on "Test"  (cost=4.26..8.27 rows=1 width=32) (actual time=0.014..0.015 rows=2 loops=1)
  Recheck Cond: ("Column1" @> '{20}'::integer[])
  ->  Bitmap Index Scan on idx_test  (cost=0.00..4.26 rows=1 width=0) (actual time=0.009..0.009 rows=2 loops=1)
        Index Cond: ("Column1" @> '{20}'::integer[])
Total runtime: 0.062 ms
Заметка

похоже, что во многих случаях требуется опция gin__int_ops

create index <index_name> on <table_name> using GIN (<column> gin__int_ops)

Я еще не видел случая, чтобы он работал с операторами && и @> без параметров gin__int_ops

Фрэнк Хайкенс
источник
20
Как предполагает OP, это фактически не индексирует отдельные значения массива, а вместо этого индексирует весь массив. Таким образом, хотя это поможет рассматриваемому запросу (см. План объяснения), это означает, что вы не можете (легко) создать уникальные ограничения для отдельных значений массива. Тем не менее, если вы используете целочисленные массивы, вы можете использовать модуль contrib «intarray» для индексации отдельных значений массива, что во многих случаях может быть намного быстрее. (IIRC, есть некоторая работа над этим для текстовых значений, но участники, вероятно, будут рады помочь закончить это).
xzilla 05
15
Пожалуйста, не используйте прописные буквы в идентификаторах PostgreSQL в примерах кода, это просто сбивает с толку людей, которые не знакомы с правилами сворачивания кавычек / регистра, особенно людей, плохо знакомых с PostgreSQL.
intgr
7
Повторяю здесь свой комментарий: по моему опыту, эти индексы практически не дают ускорения, если они gin__int_ops не используются для integer[]столбцов. Мне потребовались годы разочарования и поиск других решений, пока я не обнаружил этот операционный класс. Это пограничный чудотворец.
IamIC
3
@IamIC означает ли это, что мне не следует беспокоиться об индексировании массива строк? И я должен индексировать только целочисленные массивы?
ryan2johnson9
Класс оператора "gin__int_ops" требуется только в том случае, если вы установили расширение "intarray", в противном случае индекс работает по умолчанию. Я подробно остановился на этом здесь: stackoverflow.com/questions/63996454/…
Симон Перепелица
99

@Tregoreg поднял вопрос в комментарии к предложенной им награде:

Я не нашел, что текущие ответы работают. Использование индекса GIN для столбца с типом массива не увеличивает производительность оператора ANY (). Неужели решения нет?

В принятом ответе @Frank предлагается использовать операторы массива , что по- прежнему верно для Postgres 11. Руководство:

... стандартный дистрибутив PostgreSQL включает класс операторов GIN для массивов, который поддерживает индексированные запросы с использованием следующих операторов:

<@
@>
=
&&

Полный список классов встроенных операторов для индексов GIN в стандартной поставке находится здесь.

В Postgres индексы привязаны к операторам (которые реализованы для определенных типов), а не только к типам данных, функциям или чему-либо еще. Это наследие оригинального дизайна Postgres в Беркли, и сейчас его очень сложно изменить. И в целом работает нормально. Вот ветка pgsql-bugs, где Том Лейн комментирует это.

Некоторые функции PostGis (например, ST_DWithin()) нарушают этот принцип, но это не так. Эти функции внутренне переписаны для использования соответствующих операторов .

Проиндексированное выражение должно находиться слева от оператора. Для большинства операторов ( включая все вышеперечисленные ) планировщик запросов может добиться этого, переворачивая операнды, если вы поместите индексированное выражение вправо - при условии, что COMMUTATORбыл определен a . ANYКонструкция может быть использована в комбинации с различными операторами и не является сам оператор. При использовании constant = ANY (array_expression)только индексы, поддерживающие =оператор для элементов массива, будут квалифицированы, и нам понадобится коммутатор для = ANY(). Индексы GIN отсутствуют.

Postgres в настоящее время недостаточно умен, чтобы получить из него выражение, индексируемое GIN. Во - первых, constant = ANY (array_expression)это не полностью эквивалентны с array_expression @> ARRAY[constant]. Операторы массива возвращают ошибку, если задействованы какие-либо элементы NULL , в то время как ANYконструкция может обрабатывать NULL с любой стороны. И есть разные результаты для несоответствия типов данных.

Связанные ответы:

В сторону

При работе с integerмассивами ( int4, не int2или int8) без NULLзначений (как предполагает ваш пример) рассмотрите дополнительный модуль intarray, который предоставляет специализированные, более быстрые операторы и поддержку индексов. Увидеть:

Что касается UNIQUEограничения в вашем вопросе, который остался без ответа: это реализовано с помощью индекса btree для всего значения массива (как вы и подозревали) и вообще не помогает с поиском элементов . Детали:

Эрвин Брандштеттер
источник
1
Ааааааа, сейчас я чувствую себя довольно смущенным, но мне просто не приходило в голову, что postgres не будет использовать индекс, даже если это теоретически возможно. Возможно, это также из-за того, что я не понимаю postgres, например, что индексы привязаны к операторам. Спасибо, что нашли время ответить на мой некорректный вопрос и поделились своими знаниями!
Tregoreg
6
@Tregoreg: Не смущайся, на самом деле это не так очевидно. Я помню, что меня это самого смутило, когда я впервые столкнулся с этим. Добавленный вопрос и пояснение должны быть весьма полезны для широкой публики.
Эрвин Брандштеттер,
1
По моему опыту, эти индексы практически не дают ускорения, если они gin__int_ops не используются для integer[]столбцов. Мне потребовались годы разочарования и поиск других решений, пока я не обнаружил этот операционный класс. Это пограничный чудотворец.
IamIC
2
@IamIC: я добавил указатели на intarray. Как вы отметили, кажется примечательным.
Эрвин Брандштеттер
Для ANY (array_expression) = constantвыражений индексы GIN работают нормально?
user10375
36

Теперь можно индексировать отдельные элементы массива. Например:

CREATE TABLE test (foo int[]);
INSERT INTO test VALUES ('{1,2,3}');
INSERT INTO test VALUES ('{4,5,6}');
CREATE INDEX test_index on test ((foo[1]));
SET enable_seqscan TO off;

EXPLAIN ANALYZE SELECT * from test WHERE foo[1]=1;
                                                QUERY PLAN                                                    
------------------------------------------------------------------------------------------------------------------
 Index Scan using test_index on test  (cost=0.00..8.27 rows=1 width=32) (actual   time=0.070..0.071 rows=1 loops=1)
   Index Cond: (foo[1] = 1)
 Total runtime: 0.112 ms
(3 rows)

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

Ed4
источник
29
Пусть не заблудится - такой подход безнадежен для массива переменной длины, где вы хотите использовать оператор ANY ().
Καrτhικ 05
25
Это действительно не очень полезно. Если у вас есть фиксированное количество элементов массива, вы бы предпочли использовать отдельные столбцы для каждого элемента (и простые индексы btree) вместо создания более дорогостоящего индекса выражения для каждого элемента массива. Хранение отдельных столбцов также намного дешевле без накладных расходов на массив.
Эрвин Брандштеттер,