Un grand OFFSET
va toujours être lent. Postgres doit ordonner toutes les lignes et compter les visibles ceux jusqu'à votre décalage. Pour ignorer toutes les lignes précédentes directement vous pouvez ajouter un row_number
indexé au tableau (ou créer une MATERIALIZED VIEW
y compris ledit row_number
) et travaillez avec WHERE row_number > x
au lieu de OFFSET x
.
Cependant, cette approche n'est judicieuse que pour les données en lecture seule (ou principalement). Implémentation de la même chose pour les données de table qui peuvent changer simultanément est plus difficile. Vous devez commencer par définir exactement le comportement souhaité .
Je suggère une approche différente pour la pagination :
SELECT *
FROM big_table
WHERE (vote, id) > (vote_x, id_x) -- ROW values
ORDER BY vote, id -- needs to be deterministic
LIMIT n;
Où vote_x
et id_x
sont de la dernière rangée de la page précédente (pour les deux DESC
et ASC
). Ou du premier si vous naviguez vers l'arrière .
La comparaison des valeurs de ligne est prise en charge par l'index que vous avez déjà - une fonctionnalité conforme à la norme ISO SQL, mais tous les SGBDR ne la prennent pas en charge.
CREATE INDEX vote_order_asc ON big_table (vote, id);
Ou pour l'ordre décroissant :
SELECT *
FROM big_table
WHERE (vote, id) < (vote_x, id_x) -- ROW values
ORDER BY vote DESC, id DESC
LIMIT n;
Peut utiliser le même index.
Je vous suggère de déclarer vos colonnes NOT NULL
ou familiarisez-vous avec le NULLS FIRST|LAST
construire :
- PostgreSQL trier par datetime asc, null en premier ?
Notez deux choses en particulier :
-
Le
ROW
valeurs dansWHERE
La clause ne peut pas être remplacée par des champs de membre séparés.WHERE (vote, id) > (vote_x, id_x)
ne peut pas être remplacé par :WHERE vote >= vote_x AND id > id_xCela exclurait tous lignes avec
id <= id_x
, alors que nous ne voulons le faire que pour le même vote et pas pour le suivant. La traduction correcte serait :WHERE (vote = vote_x AND id > id_x) OR vote > vote_x
... qui ne s'adapte pas aussi bien aux index et devient de plus en plus compliqué pour plus de colonnes.
Ce serait simple pour un célibataire colonne, évidemment. C'est le cas particulier que j'ai mentionné au début.
-
La technique ne fonctionne pas pour les directions mixtes dans
ORDER BY
comme :ORDER BY vote ASC, id DESC
Au moins, je ne peux pas penser à un générique moyen de le mettre en œuvre aussi efficacement. Si au moins une des deux colonnes est de type numérique, vous pouvez utiliser un index fonctionnel avec une valeur inversée sur
(vote, (id * -1))
- et utiliser la même expression dansORDER BY
:ORDER BY vote ASC, (id * -1) ASC
Connexe :
- Terme de syntaxe SQL pour 'WHERE (col1, col2) <(val1, val2)'
- Améliorer les performances du tri par avec des colonnes provenant de nombreux tableaux
Notez en particulier la présentation de Markus Win et j'ai lié à :
- "Pagination faite à la manière de PostgreSQL"