Utilisation de PostgreSQL 10.5 . J'essaie de créer un système de pagination où l'utilisateur peut aller et venir entre divers résultats.
Pour ne pas utiliser OFFSET
, je passe le id
de la dernière ligne de la page précédente dans un paramètre appelé p
(prevId). Je sélectionne ensuite les trois premières lignes dont id
est supérieur au nombre passé dans le paramètre p
. (comme décrit dans cet article )
Par exemple, si le id
pour la dernière ligne de la page précédente était 5, je sélectionnerais les 3 premières lignes avec un id
supérieur à 5:
SELECT
id,
firstname,
lastname
FROM
people
WHERE
firstname = 'John'
AND id > 5
ORDER BY
ID ASC
LIMIT
3;
Cela fonctionne très bien et le timing n'est pas très mal non plus:
Limit (cost=0.00..3.37 rows=3 width=17) (actual time=0.046..0.117 rows=3 loops=1)
-> Seq Scan on people (cost=0.00..4494.15 rows=4000 width=17) (actual time=0.044..0.114 rows=3 loops=1)
Filter: ((id > 5) AND (firstname = 'John'::text))
Rows Removed by Filter: 384
Planning time: 0.148 ms
Execution time: 0.147 ms
Si, en revanche, l'utilisateur souhaite revenir à la page précédente, les choses semblent un peu différentes:
Tout d'abord, je passerais le id
pour la première ligne, puis je mettrais le signe moins devant pour indiquer que je devrais sélectionner les lignes avec un id
inférieur à (positif) p
paramètre. A savoir, si le id
pour la première ligne est 6, le paramètre p
serait -6
. De même, ma requête ressemblerait à ceci:
SELECT
*
FROM
(
SELECT
id,
firstname,
lastname
FROM
people
WHERE
firstname = 'John'
AND id < 6
ORDER BY
id DESC
LIMIT
3
) as d
ORDER BY
id ASC;
Dans la requête ci-dessus, je sélectionne d'abord les 3 dernières lignes avec un id
inférieur à 6, puis je les inverse pour les présenter de la même manière que la première requête décrite au début.
Cela fonctionne comme il se doit, mais comme la base de données parcourt presque toutes mes lignes, les performances en souffrent:
Sort (cost=4252.75..4252.76 rows=1 width=17) (actual time=194.464..194.464 rows=0 loops=1)
Sort Key: people.id
Sort Method: quicksort Memory: 25kB
-> Limit (cost=4252.73..4252.73 rows=1 width=17) (actual time=194.460..194.460 rows=0 loops=1)
-> Sort (cost=4252.73..4252.73 rows=1 width=17) (actual time=194.459..194.459 rows=0 loops=1)
Sort Key: people.id DESC
Sort Method: quicksort Memory: 25kB
-> Gather (cost=1000.00..4252.72 rows=1 width=17) (actual time=194.448..212.010 rows=0 loops=1)
Workers Planned: 1
Workers Launched: 1
-> Parallel Seq Scan on people (cost=0.00..3252.62 rows=1 width=17) (actual time=18.132..18.132 rows=0 loops=2)
Filter: ((id < 13) AND (firstname = 'John'::text))
Rows Removed by Filter: 100505
Planning time: 0.116 ms
Execution time: 212.057 ms
Cela étant dit, j'apprécie que vous ayez pris le temps de lire jusqu'ici et ma question est, comment puis-je rendre la pagination plus efficace?
La clé de la performance est un index multicolonne correspondant de la forme:
CREATE UNIQUE INDEX ON people (firstname, id);
UNIQUE
, car l'ordre de tri peut être ambigu sans lui, et vous pouvez obtenir des résultats arbitraires de vos pairs.
A UNIQUE
ou PRIMARY KEY
la contrainte sert aussi.
Alors que la première colonne est vérifiée pour l'égalité comme dans votre exemple (ou triée dans le même sens que la requête), cet index est bon pour la pagination de haut en bas, bien qu'il soit un peu mieux pour la pagination en haut.
Avec l'index en place (et après avoir exécuté ANALYZE
sur la table), vous ne verrez plus d'analyses séquentielles (sauf si votre table est petite). La base de données ne "passe plus par presque toutes vos lignes".
Lisez l'amende présentation de Markus Winand vous liez.
Si vous souhaitez paginer sur plusieurs firstname
, utilisez les valeurs ROW. Exemple de pagination vers le bas:
SELECT *
FROM (
SELECT id, firstname, lastname
FROM people
WHERE (firstname, id) < ('John', 6) -- ROW values
ORDER BY firstname DESC, id DESC
LIMIT 3
) d
ORDER BY firstname, id;
En relation:
Si la liste SELECT
n'ajoute que lastname
comme dans votre exemple, vous pouvez essayer d'ajouter cette colonne à l'index pour obtenir analyses d'index uniquement hors de celui-ci:
CREATE UNIQUE INDEX ON people (firstname, id, lastname);
Indexez les expressions dans cet ordre.
Le prochain Postgres 11 permet aux index de INCLUDE
colonnes , ce qui se traduit par une taille d'index plus petite, de meilleures performances et est applicable dans plus de situations. Comme:
CREATE UNIQUE INDEX ON people (firstname, id) INCLUDE (lastname);