Recherches PostgreSQL par clé primaire en lots

Si le code applicatif envoie une requête par ID, les allers-retours réseau et le coût des requêtes peuvent dominer une petite lecture de ligne. Commencez par essayer une instruction paramétrée :

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

Passez les ID comme paramètre de tableau. Gardez la table et les colonnes fixes dans l’instruction ; ne construisez pas le SQL à partir de chaînes d’ID. PostgreSQL évalue ANY en comparant l’expression de gauche aux éléments du tableau, comme l’expliquent ses documents sur les comparaisons de lignes et de tableaux.

Connaître le contrat de résultat

La requête ci-dessus renvoie un ensemble. Elle ne promet pas l’ordre d’entrée, et un ID dupliqué correspond normalement une seule fois à la ligne de la table. Les ID absents ne produisent aucune ligne. Une entrée NULL ne correspond pas à une clé primaire non nulle ; un tableau nul ou des éléments nuls suivent aussi les règles ANY à trois valeurs de PostgreSQL. Un tableau vide ne renvoie aucune ligne.

Si l’appelant a besoin d’un résultat pour chaque position demandée, conservez explicitement les positions :

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 conserve les doublons et les positions NULL ; la jointure externe gauche renvoie une ligne row nulle pour une clé absente. C’est une bonne référence pour un client qui a besoin d’un alignement explicite. Consultez l’exemple node-postgres pour restaurer le même contrat côté client.

Quand mget est la bonne alternative

Pour des lignes complètes par clé primaire, pg_local_cache offre une API de lots explicite et bornée :

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

Le tableau text[] renvoyé conserve l’ordre d’entrée et les doublons. Les entrées NULL et les lignes absentes produisent des éléments NULL alignés. Les appels acceptent au plus 1 024 clés, et la fonction peut contourner ou manquer le cache selon les règles de transaction, de snapshot, de mapping et de taille de ligne ; elle revient à PostgreSQL sans changer le contrat de résultat. Elle renvoie des lignes complètes sérialisées ; utilisez donc ANY ou la requête avec ordinality si vous avez besoin d’une projection, de jointures, de filtres au-delà de la clé ou d’un lot non borné.

GraphQL, DataLoader et lectures N+1

DataLoader regroupe les chargements individuels en un lot. Sa fonction de lot doit renvoyer une valeur par clé d’entrée dans le même ordre ; la restauration ci-dessus fournit cette forme même pour les lignes absentes.

La mémoïsation par requête de DataLoader est séparée du cache de lignes partagé de PostgreSQL. Créez des loaders pour chaque requête et supprimez les entrées concernées après les mutations de cette requête. L’invalidation PostgreSQL ne peut pas vider les valeurs déjà stockées dans un loader JavaScript. Conservez les contrôles d’autorisation applicatifs ; pg_local_cache ne prend pas en charge les tables RLS.

Lancez le démarrage rapide, puis comparez les deux chemins de lecture dans les benchmarks. La référence technique définit l’API ; le guide des transactions couvre les écritures.

Mis à jour .