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

107

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

Я создал простую таблицу с одним столбцом int[] и поместил на нее уникальный индекс. Я заметил, что я не могу добавить один и тот же массив 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");

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

Теги:
arrays
indexing

3 ответа

139
Лучший ответ

Да, вы можете индексировать массив, но вы должны использовать операторы массива и тип индекса 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

  • 16
    Как предполагает OP, это на самом деле не индексирует отдельные значения массива, а вместо этого индексирует весь массив. Таким образом, хотя это поможет рассматриваемому запросу (см. План объяснения), это означает, что вы не можете создавать (легко) уникальные ограничения для отдельных значений массива. Тем не менее, если вы используете целочисленные массивы, вы можете использовать модуль contrib «intarray» для индексации отдельных значений массива, что может быть намного быстрее во многих случаях. (IIRC уже проделана определенная работа над текстовыми значениями, но участники, вероятно, будут рады помочь завершить ее).
  • 13
    Пожалуйста, не используйте заглавные буквы в идентификаторах PostgreSQL в примерах кода, это просто сбивает с толку людей, которые не знакомы с правилами свертывания / цитирования, особенно тех, кто плохо знаком с PostgreSQL.
Показать ещё 2 комментария
75

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

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

@Frank принял ответ говорит вам использовать операторы массива, который до сих пор правильно для Postgres 11. Учебное пособие:

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

<@
@>
=
&&

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

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

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

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

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

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

Asides

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

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

  • 0
    А-а-а-а-а-а, сейчас я чувствую себя довольно неловко, но мне просто не пришло в голову, что Postgres не будет использовать этот индекс, даже если это теоретически возможно. Может быть, это также потому, что мое отсутствие понимания postgres, например, что индексы связаны с операторами. Спасибо, что нашли время ответить на мой некорректный вопрос и поделились своими знаниями!
  • 6
    @Tregoreg: Не смущайтесь, это не так очевидно. Я помню, что сам был смущен, когда впервые столкнулся с этим. Добавленный вопрос и разъяснения должны быть весьма полезными для широкой публики.
Показать ещё 4 комментария
31

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

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. Обратите внимание, что вам нужно создать отдельный индекс для каждого индекса массива, в моем примере я только проиндексировал первый элемент.

  • 23
    Пусть это не будет потеряно - этот подход безнадежен для массива переменной длины, где вы хотите использовать оператор ANY ().
  • 16
    Это действительно не очень полезно. Если у вас есть фиксированное количество элементов массива, вы предпочтете использовать отдельные столбцы для каждого элемента (и простые индексы дерева) вместо создания более дорогого индекса выражения для каждого элемента массива. Хранение отдельных столбцов также обходится намного дешевле без затрат на массив.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню