PostgreSQL не использует частичный индекс

У меня есть таблица в PostgreSQL 9.2, которая имеет столбец text. Позвольте называть это text_col. Значения в этом столбце довольно уникальны (могут содержать не более 5-6 дубликатов). Таблица имеет ~ 5 миллионов строк. Около половины этих строк содержат значение null для text_col. Когда я выполняю следующий запрос, я ожидаю 1-5 строк. В большинстве случаев ( > 80%) я ожидаю только 1 строку.

Query

explain analyze SELECT col1,col2.. colN
FROM table 
WHERE text_col = 'my_value';

A btree индекс существует на text_col. Этот индекс никогда не используется планировщиком запросов, и я не уверен, почему. Это результат запроса.

Планировщик

Seq Scan on two (cost=0.000..459573.080 rows=93 width=339) (actual time=1392.864..3196.283 rows=2 loops=1)
Filter: (victor = 'foxtrot'::text)
Rows Removed by Filter: 4077384

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

CREATE INDEX name_idx
  ON table
  USING btree
  (text_col COLLATE pg_catalog."default" text_pattern_ops)
  WHERE text_col IS NOT NULL;

Отключение сканирования последовательностей с помощью set enable_seqscan = off; заставляет планировщик по-прежнему выбирать seqscan поверх index_scan. В итоге...

  • Количество строк, возвращаемых этим запросом, невелико.
  • Учитывая, что ненулевые строки довольно уникальны, сканирование индекса по тексту должно быть быстрее.
  • Вакуумирование и анализ таблицы не помогли оптимизатору выбрать индекс.

Мои вопросы

  • Почему база данных выбирает сканирование последовательности по сканированию индекса?
  • Когда в таблице есть текстовый столбец, условие равенства которого должно быть проверено, существуют ли какие-либо рекомендации, к которым я могу присоединиться?
  • Как сократить время, затрачиваемое на этот запрос?

[Изменить - Дополнительная информация]

  • Сканирование индекса происходит в моей локальной базе данных, где хранится около 10% данных, доступных в процессе производства.

Ответ 1

A частичный индекс является хорошей идеей, чтобы исключить половину строк таблицы, которые вы, очевидно, не используете необходимость. Simpler:

CREATE INDEX name_idx ON table (text_col)
WHERE text_col IS NOT NULL;

Обязательно запустите ANALYZE table после создания индекса. (Autovacuum делает это автоматически через некоторое время, если вы не делаете это вручную, но если вы проверите сразу после создания, ваш тест не удастся.)

Затем, чтобы убедить планировщика запросов, что может использоваться конкретный частичный индекс, повторите условие WHERE в запросе - даже если оно кажется полностью избыточным:

SELECT col1,col2, .. colN
FROM   table 
WHERE  text_col = 'my_value'
AND   text_col IS NOT NULL;  -- repeat condition

Вуаля.

В документации:

Однако помните, что предикат должен соответствовать условиям используется в запросах, которые должны получать выгоду от индекса. Быть точный, неполный индекс может использоваться в запросе только в том случае, если система может признать, что условие WHERE запроса математически подразумевает предикат индекса. PostgreSQL не имеет сложного доказательство теоремы, которое может распознавать математически эквивалентный выражения, написанные в разных формах. (Это не только такой общий теоретический прорыв чрезвычайно сложно создать, он вероятно, слишком медленно, чтобы быть реальным использованием.) Система может распознать простые последствия неравенства, например, "x < 1" означает "x < 2"; в противном случае условие предиката должно точно соответствовать части query WHERE или индекс не будет признан пригодным для использования. Согласование происходит во время планирования запроса, а не во время выполнения. Как результат, параметризованные предложения запроса не работают с частичным индексом.

Что касается параметризованных запросов: снова добавьте (избыточный) предикат частичного индекса в качестве дополнительного константного условия WHERE, и он отлично работает.


Важное обновление в Postgres 9.6 в значительной степени повышает шансы просмотр только по индексу (что может сделать запросы дешевле, и планировщик запросов будет более легко выбирать такие планы запросов). Связанный:

Ответ 2

Частичный индекс используется только в том случае, если условия WHERE совпадают. Таким образом, индекс с WHERE text_col IS NOT NULL может использоваться только в том случае, если вы используете те же условия в SELECT. Сопоставление рассогласования также может нанести вред.

Попробуйте следующее:

  • Сделайте простейший возможный индекс btree CREATE INDEX foo ON table (text_col)
  • ANALYZE table
  • Запрос

Ответ 3

Я понял это. При внимательном рассмотрении представления pg_stats, которое помогает analyze, я наткнулся на эту выдержку в документации.

Корреляция

Статистическая корреляция между упорядочением физических строк и логическими упорядочение значений столбца. Это от -1 до +1. Когда значение около -1 или +1, сканирование индекса по столбцу будет оценено быть дешевле, чем когда он близок к нулю, из-за уменьшения случайных доступ к диску. (Этот столбец равен NULL, если тип данных столбца не имеют < оператор.)

В моем локальном поле число корреляции 0.97, а при его производстве было 0.05. Таким образом, планировщик оценивает, что легче проходить через все эти строки последовательно, а не искать индекс каждый раз и погружаться в произвольный доступ к блоку диска. Это запрос, который я использовал для просмотра номера корреляции.

select * from pg_stats where tablename = 'table_name' and attname = 'text_col';

В этой таблице также есть несколько обновлений, выполненных в его строках. Строка avg_width для строк оценивается в 20 байтов. Если обновление имеет большое значение для текстового столбца, оно может превышать среднее значение и также приводит к более медленному обновлению. Мое предположение заключалось в том, что физическое и логическое упорядочение замедляются, двигаясь вместе с каждым обновлением. Чтобы исправить это, я выполнил следующие запросы.

ALTER TABLE table_name SET (FILLFACTOR = 80);
VACUUM FULL table_name;
REINDEX TABLE table_name;
ANALYZE table_name;

Идея состоит в том, что я могу предоставить каждому блоку диска 20% -ный буфер и vacuum full таблицу, чтобы вернуть потерянное пространство и поддерживать физический и логический порядок. После этого запрос забирает индекс.

Query

explain analyze SELECT col1,col2... colN
FROM table_name 
WHERE text_col is not null 
AND 
text_col = 'my_value';

Сканирование частичного индекса - 1,5 мс

Index Scan using tango on two (cost=0.000..165.290 rows=40 width=339) (actual time=0.083..0.086 rows=1 loops=1)
Index Cond: ((victor five NOT NULL) AND (victor = 'delta'::text))

Исключая условие NULL, подбирает другой индекс с помощью сканирования кучи растровой карты.

Полный индекс - 0.08мс

Bitmap Heap Scan on two  (cost=5.380..392.150 rows=98 width=339) (actual time=0.038..0.039 rows=1 loops=1)
    Recheck Cond: (victor = 'delta'::text)
  ->  Bitmap Index Scan on tango  (cost=0.000..5.360 rows=98 width=0) (actual time=0.029..0.029 rows=1 loops=1)
          Index Cond: (victor = 'delta'::text)

[EDIT]

В то время как изначально он выглядел так, как correlation играет важную роль в выборе сканирования индекса @Mike заметил, что значение correlation, близкое к 0 в его базе данных, все же привело к сканированию индекса. Изменение коэффициента заполнения и вакуумирования полностью помогло, но я не уверен, почему.