Как заставить Postgres использовать индекс для набора значений?

У меня есть таблица с примерно 35 миллионами строк, и я пытаюсь найти «обработанные» записи, которые можно время от времени удалять. Действующих статусов 14, из них 10 обрабатываются.

id uuid default uuid_generate_v4() not null primary key,
fk_id uuid not null references fk_table,
-- ... other columns
created_date timestamptz default now() not null,
status varchar(128) not null

Значения статуса могут быть одним из a,b,c,d,e,f,g,h,i,j,k,l,m,n (14)

Индекс включен (status,created_date).
Запрос типа:

select id from table
where created_date < 'somedate'
and status = ANY('{a,b,c,d,e,f,g,h,i,j}') -- one of first 10 values

Планировщик запросов настаивает на использовании полного seq_scan вместо индекса.

Есть ли способ заставить Postgres использовать индекс для части предиката status = ANY?

Не могли бы вы поделиться результатами explain(analyze, verbose, buffers, settings и полным DDL для всех задействованных таблиц и индексов? Все в виде обычного текста, как обновление исходного вопроса.

Frank Heikens 04.06.2024 20:38

@FrankHeikens Я посмотрю, что смогу сделать.

JJS 04.06.2024 21:50

Использует ли он индекс, если вы используете синтаксис IN ( … )?

Bergi 05.06.2024 01:46

@Bergi нет, в любом случае он не использует индекс.

JJS 05.06.2024 05:03

@Bergi, в ответе Эрвина Брандштеттера есть отличная SQL-скрипт, демонстрирующая различия в поведении массива.

JJS 05.06.2024 20:47
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
95
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Если более нескольких процентов строк соответствуют критериям (или, скорее, если Postgres оценивает это количество), он выберет последовательное сканирование, которое в таком случае быстрее.

Если на самом деле соответствует требованиям только несколько строк, то статистика вашего столбца (и/или настройки стоимости) виновата в вводящих в заблуждение оценках.

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

CREATE INDEX foo ON tbl (created_date) WHERE status = ANY('{a,b,c..10}')

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

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

И более агрессивные autovacuum настройки стола:

В любом случае, только для «14 действительных состояний» status varchar(128) кажется чрезвычайно расточительным.

Кроме того, планировщик в Postgres 16 стал умнее по сравнению с Postgres 12. Postgres 16 обнаруживает общие значения даже во входных массивах с большим количеством элементов (намного больше, чем у вас есть отдельные значения в status), и соответствующим образом переключает план. Не уверен насчет старой логики в Postgres 12, но более чем? элементы в массиве, Postgres использовал для переключения на общие оценки, что может привести к плохим результатам.

Но обратите внимание, что любая версия может даже адаптировать план для подготовленных утверждений на основе фактических данных.

рабочий пример -- стр. 16
рабочий пример -- стр. 12

Дальше зависит от недостающих деталей...

Может ли postgres на самом деле выполнить оценку, если сравниваемое значение является массивом, знает ли он длину и элементы массива? Что, если значение массива будет передано как параметр (а не как литерал SQL в тексте запроса)?

Bergi 05.06.2024 02:28

@Bergi значение массива передается как параметр

JJS 05.06.2024 05:05

@JJS Это было мое предположение, и я боюсь, что это одна из вещей, которые могут повлиять на планировщик, поэтому спасибо за разъяснение этой детали. Будучи динамическим подмножеством состояний, безусловно, делает недействительной идею использования частичного индекса.

Bergi 05.06.2024 05:09

@Erwin Brandstetter Я бы подумал, что задействованные предикаты смогут эффективно использовать индекс. Это потому, что созданный план запроса является общим и не понимает значения параметров? Должен ли я включить это в свой первоначальный вопрос? > и лишь относительно небольшое количество строк имеют «обработанное» состояние, замените его частичным индексом: я попробовал это и не получил лучших результатов. Возможно, я сделал что-то не так. Я прочитаю предоставленные ссылки.

JJS 05.06.2024 05:11

Postgres может использовать оценки элементов массива во входных данных с некоторыми ограничениями. Подготовленный оператор более ограничен, но он все равно может адаптировать план к входным данным. Однако Postgres 16 справляется с этим намного лучше, чем Postgres 12. я добавил чуть выше

Erwin Brandstetter 05.06.2024 05:23

@ErwinBrandstetter спасибо за эту поправку и дополнительные мысли. Это дает мне дополнительные мысли, которые мне нужны, а также дает мне мотивацию перейти с PG12 на 16! >Больше зависит от недостающих деталей... Извините, я не смог предоставить больше. Вы фантастически справились с тем, что я предоставил.

JJS 05.06.2024 15:54

Вероятно, он просто думает, что последовательное сканирование будет быстрее, и, насколько нам известно, он прав в том, что оно быстрее. Вы можете заставить его попробовать оба плана, изменив настройку «enable_seqscan» и получив EXPLAIN (ANALYZE, BUFFERS) под каждой настройкой. Таким образом, мы сможем увидеть, какой из них на самом деле быстрее (запустите его несколько раз в каждом направлении, чтобы убедиться, что для каждого из них получено согласованное время, а не просто однократная случайность), и каковы его предполагаемые и подсчитанные строки, чтобы увидеть, являются ли они противоречивы.

Если существует корреляция столбцов, так что в этих 10 статусах выборочно отсутствует низкое значение созданной_даты, этот перекос может сделать невозможным получение точных оценок количества строк. Этот тип перекоса вполне вероятен, учитывая ваше описание того, как строки удаляются из таблицы. И ни один из реализованных в настоящее время типов расширенной статистики, вероятно, не сможет решить проблему такого типа оценки.

Но независимо от этого, если вы создадите правильный индекс, чтобы он мог использовать сканирование только индекса, (status,created_date,id), он на самом деле должен быть быстрее и также должен быть оценен как более быстрый, и поэтому, скорее всего, будет использовать этот индекс, даже если оценки строки оставаться неправым. Это с большей вероятностью сработает, чем частичный индекс, предложенный Эрвином, потому что (увы) PostgreSQL не использует размер частичных индексов в процессе оценки. Таким образом, даже если индекс может быть небольшим, PostgreSQL не будет использовать эти знания для использования частичного индекса.

Я выздоравливающий разработчик MSSQL. В этом механизме все некластеризованные индексы неявно содержат ключевые столбцы кластеризованного индекса для этой строки как часть своего определения. Вы хотите сказать, что в postgres ключ кластеризованного индекса не является неявной частью определения некластеризованного индекса, и вам нужно явно включить его? Я уверен, что некластеризованный индекс должен будет включать как минимум ctid, но мне кажется, что вы здесь говорите о том, что после сканирования некластеризованного индекса у него есть список ctid, который он должен вернитесь в кучу, чтобы обменять ctid. Верно?

JJS 05.06.2024 15:51

это золото. можно ссылку на источник? > PostgreSQL не использует размер частичных индексов в процессе оценки. Я считаю, что MSSQL заботится о размере индекса (страниц или общий размер, не помню)

JJS 05.06.2024 15:52

@JJS PostgreSQL не имеет кластеризованных индексов (по крайней мере, в том смысле, в котором MSSQL использует этот термин). Все индексы вторичны. Первичный ключ не является автоматически частью каждого индекса. Если вы хотите иметь возможность извлекать столбец первичного ключа из индекса, вам необходимо явно включить его в индекс. Так что да, в противном случае ему необходимо посетить таблицу, чтобы «обменять» ctid на ПК.

jjanes 05.06.2024 19:18

@JSS Я не думаю, что для этого есть источник. Просто не хватает источника. То есть исходного кода, использующего эту информацию, не существует, несмотря на то, что он доступен. Нереализованные функции обычно не документируются, если только это не необходимо для документирования отклонения от стандарта, но здесь это не применимо. Размер индекса используется для оценки затрат на ввод-вывод, а не для оценки строк.

jjanes 05.06.2024 20:38

> Спасибо, в PostgreSQL нет кластерных индексов. Мне еще многое предстоит узнать о том, чем PostgreSQL отличается от MSSQL. Можете ли вы указать мне, где описывается, каким целям служит первичный ключ и как он влияет на порядок кучи?

JJS 05.06.2024 20:46

Другие вопросы по теме