🛠️ GIN-индекс есть, но фильтр по JSONB всё равно идёт через Seq Scan
Индекс:
CREATE INDEX events_payload_gin
ON events
USING gin (payload);
Запрос приложения:
SELECT *
FROM events
WHERE payload->>'status' = 'paid';
План:
Seq Scan on events
Filter: ((payload ->> 'status') = 'paid')
Причина может быть не в том, что PostgreSQL «не видит» индекс.
GIN создан на исходной колонке payload, а запрос сравнивает результат выражения:
payload->>'status'
->> возвращает text. Поэтому это равенство по вычисленному выражению, а не containment-поиск по всей jsonb-колонке.
Сравните:
WHERE payload @> '{"status": "paid"}'::jsonb
Здесь @> применяется непосредственно к payload. Такая форма может соответствовать GIN-индексу на колонке.
А здесь:
WHERE payload->>'status' = 'paid'
для устойчивого фильтра может потребоваться expression index:
CREATE INDEX events_status_idx
ON events ((payload->>'status'));
Для текстового равенства это обычно B-tree expression index.
Но создавать его автоматически не стоит.
Проверьте:
— часто ли используется именно это выражение;
— сколько строк соответствует значению;
— не выбирает ли запрос большую часть таблицы;
— как часто изменяется JSONB;
— есть ли дополнительные фильтры;
— что показывают Filter, Index Cond и Recheck Cond;
— как меняется план с реальными параметрами.
Важно: Seq Scan сам по себе не доказывает проблему. Для маленькой таблицы или низкой селективности он может быть дешевле индекса.
И не переписывайте любое равенство на @> только ради GIN. Нужно сохранить правильную семантику для отсутствующих ключей, JSON null, типов значений и вложенной структуры.
Вывод: индекс «по JSONB» не является индексом для любого обращения к JSON. Сначала определите точную форму рабочего фильтра, затем выбирайте подходящий индекс.
Сохраните пример для случаев, когда «индекс есть», но план всё равно показывает Seq Scan.
🔹🔹🔹🔹
Индекс:
CREATE INDEX events_payload_gin
ON events
USING gin (payload);
Запрос приложения:
SELECT *
FROM events
WHERE payload->>'status' = 'paid';
План:
Seq Scan on events
Filter: ((payload ->> 'status') = 'paid')
Причина может быть не в том, что PostgreSQL «не видит» индекс.
GIN создан на исходной колонке payload, а запрос сравнивает результат выражения:
payload->>'status'
->> возвращает text. Поэтому это равенство по вычисленному выражению, а не containment-поиск по всей jsonb-колонке.
Сравните:
WHERE payload @> '{"status": "paid"}'::jsonb
Здесь @> применяется непосредственно к payload. Такая форма может соответствовать GIN-индексу на колонке.
А здесь:
WHERE payload->>'status' = 'paid'
для устойчивого фильтра может потребоваться expression index:
CREATE INDEX events_status_idx
ON events ((payload->>'status'));
Для текстового равенства это обычно B-tree expression index.
Но создавать его автоматически не стоит.
Проверьте:
— часто ли используется именно это выражение;
— сколько строк соответствует значению;
— не выбирает ли запрос большую часть таблицы;
— как часто изменяется JSONB;
— есть ли дополнительные фильтры;
— что показывают Filter, Index Cond и Recheck Cond;
— как меняется план с реальными параметрами.
Важно: Seq Scan сам по себе не доказывает проблему. Для маленькой таблицы или низкой селективности он может быть дешевле индекса.
И не переписывайте любое равенство на @> только ради GIN. Нужно сохранить правильную семантику для отсутствующих ключей, JSON null, типов значений и вложенной структуры.
Вывод: индекс «по JSONB» не является индексом для любого обращения к JSON. Сначала определите точную форму рабочего фильтра, затем выбирайте подходящий индекс.
Сохраните пример для случаев, когда «индекс есть», но план всё равно показывает Seq Scan.
🔹🔹🔹🔹