Пакетное чтение по первичному ключу PostgreSQL

Если код приложения отправляет по одному запросу на каждый ID, сетевые обмены с сервером и накладные расходы запроса могут доминировать в небольшом чтении строки. Сначала попробуйте один параметризованный оператор:

SELECT id, value, revision
FROM public.items
WHERE id = ANY($1::bigint[]);

Передавайте ID как параметр-массив. Таблицу и столбцы оставляйте фиксированными в операторе; не собирайте SQL из строк ID. PostgreSQL вычисляет ANY, сравнивая левое выражение с элементами массива, как описано в документации о сравнении строк и массивов.

Знайте контракт результата

Приведённый выше запрос возвращает набор. Он не обещает порядок входных данных, а дублирующийся ID обычно сопоставляется с одной строкой таблицы один раз. Отсутствующие ID не дают строк. Входной NULL не сопоставляется с ненулевым первичным ключом; массив null и элементы null также следуют трёхзначной логике ANY PostgreSQL. Пустой массив не возвращает строк.

Если вызывающей стороне нужен один результат для каждой запрошенной позиции, сохраните позиции явно:

WITH requested AS (
  SELECT key, position
  FROM unnest($1::bigint[]) WITH ORDINALITY AS input(key, position)
)
SELECT requested.position,
       requested.key,
       CASE WHEN items.id IS NULL THEN NULL
            ELSE row_to_json(items)::text END AS row
FROM requested
LEFT JOIN public.items AS items ON items.id = requested.key
ORDER BY requested.position;

WITH ORDINALITY сохраняет дубликаты и позиции NULL; левое соединение возвращает null в row для отсутствующего ключа. Это полезная базовая линия для клиента, которому нужно явное выравнивание. См. пример node-postgres, где показано клиентское восстановление того же контракта.

Когда mget — подходящая альтернатива

Для целых строк по первичному ключу pg_local_cache предлагает явный ограниченный API пакетного чтения:

SELECT local_cache.mget(
  'public.items'::regclass,
  $1::bigint[]
) AS rows;

Возвращаемый text[] сохраняет порядок входных данных и дубликаты. Входной NULL и отсутствующие строки дают выровненные элементы NULL. Вызов принимает не более 1 024 ключей, а кэш может быть обойдён или не использован согласно правилам транзакции, снимка, сопоставления и размера строки; в таком случае он переходит к PostgreSQL, не меняя контракт результата. Возвращаются целые сериализованные строки, поэтому для проекции, соединений, фильтров помимо ключа или неограниченного пакета используйте ANY или запрос с ordinality.

GraphQL, DataLoader и чтение N+1

DataLoader объединяет отдельные загрузки в пакет. Его пакетная функция должна возвращать по одному значению на каждый входной ключ в том же порядке; приведённое выше восстановление даёт такую форму даже для отсутствующих строк.

Мемоизация DataLoader на запрос отдельна от общей памяти кэша строк PostgreSQL. Создавайте загрузчики для каждого запроса и очищайте затронутые записи загрузчика после мутаций в этом запросе. Инвалидация PostgreSQL не может очистить значения, уже сохранённые в загрузчике JavaScript. Сохраняйте проверки авторизации приложения; pg_local_cache не поддерживает таблицы RLS.

Запустите quickstart, затем сравните оба пути чтения в бенчмарках. Технический справочник определяет API; руководство по транзакциям посвящено записям.

Обновлено .