MySQL Data - La meilleure façon d'implémenter la pagination?

209

Mon application iPhone se connecte à mon service Web PHP pour récupérer des données à partir d'une base de données MySQL. Une demande peut renvoyer 500 résultats.

Quelle est la meilleure façon d'implémenter la pagination et de récupérer 20 éléments à la fois?

Disons que je reçois les 20 premières annonces de ma base de données. Maintenant, comment puis-je demander les 20 prochaines annonces?

aryaxt
la source

Réponses:

310

De la documentation MySQL :

La clause LIMIT peut être utilisée pour limiter le nombre de lignes renvoyées par l'instruction SELECT. LIMIT prend un ou deux arguments numériques, qui doivent tous deux être des constantes entières non négatives (sauf lors de l'utilisation d'instructions préparées).

Avec deux arguments, le premier argument spécifie le décalage de la première ligne à renvoyer et le second spécifie le nombre maximum de lignes à renvoyer. Le décalage de la ligne initiale est 0 (pas 1):

SELECT * FROM tbl LIMIT 5,10;  # Retrieve rows 6-15

Pour récupérer toutes les lignes d'un certain décalage jusqu'à la fin de l'ensemble de résultats, vous pouvez utiliser un grand nombre pour le deuxième paramètre. Cette instruction récupère toutes les lignes de la 96e ligne à la dernière:

SELECT * FROM tbl LIMIT 95,18446744073709551615;

Avec un argument, la valeur spécifie le nombre de lignes à renvoyer depuis le début du jeu de résultats:

SELECT * FROM tbl LIMIT 5;     # Retrieve first 5 rows

En d'autres termes, LIMIT row_count est équivalent à LIMIT 0, row_count.

Faisal Feroz
la source
108
Lorsque vous utilisez LIMIT pour la pagination, vous devez également spécifier un ORDER BY.
Mark Byers
10
@shylent: Rien de mal à citer la documentation, mais je conviens qu'il aurait dû mentionner qu'il copiait les documents et fournir un lien vers la source d'origine. Je suis également surpris que la documentation inclue des exemples d'utilisation de LIMIT sans ORDER BY ... ce qui semble être une mauvaise pratique à encourager. Sans une COMMANDE PAR, il n'y a aucune garantie que la commande sera la même entre les appels.
Mark Byers
13
de toute façon, lors de la pagination de grands ensembles de résultats (et c'est à cela que sert la pagination - divisez les grands ensembles de résultats en petits morceaux, non?), vous devez garder à l'esprit que si vous faites un limit X, Y, ce qui se passe essentiellement est que les lignes X + Y sont récupérées, puis X lignes depuis le début sont supprimées et tout ce qui reste est retourné. Pour réitérer: limit X, Yrésulte en un balayage des lignes X + Y.
shylent
7
Je n'aime pas votre idée LIMIT 95, 18446744073709551615 .. jetez un œil à OFFSET;-)
CharlesLeaf
5
Ce n'est pas efficace lorsque vous travaillez avec des données volumineuses. Consultez codular.com/implementing-pagination pour plusieurs façons qui conviennent à un scénario spécifique.
Amit
125

Pour 500 enregistrements, l'efficacité n'est probablement pas un problème, mais si vous avez des millions d'enregistrements, il peut être avantageux d'utiliser une clause WHERE pour sélectionner la page suivante:

SELECT *
FROM yourtable
WHERE id > 234374
ORDER BY id
LIMIT 20

Le "234374" est ici l'identifiant du dernier enregistrement de la page précédente que vous avez consultée.

Cela permettra à un index sur id d'être utilisé pour trouver le premier enregistrement. Si vous l'utilisez, LIMIT offset, 20vous constaterez que cela devient de plus en plus lent lorsque vous naviguez vers la fin. Comme je l'ai dit, cela n'aura probablement pas d'importance si vous n'avez que 200 enregistrements, mais cela peut faire la différence avec des ensembles de résultats plus importants.

Un autre avantage de cette approche est que si les données changent entre les appels, vous ne manquerez pas d'enregistrements ou n'obtiendrez pas un enregistrement répété. En effet, l'ajout ou la suppression d'une ligne signifie que le décalage de toutes les lignes après sa modification. Dans votre cas, ce n'est probablement pas important - je suppose que votre pool d'annonces ne change pas trop souvent et de toute façon personne ne remarquerait s'il reçoit la même annonce deux fois de suite - mais si vous cherchez la "meilleure façon" alors c'est une autre chose à garder à l'esprit lors du choix de l'approche à utiliser.

Si vous souhaitez utiliser LIMIT avec un décalage (et cela est nécessaire si un utilisateur accède directement à la page 10000 au lieu de parcourir les pages une par une), vous pouvez lire cet article sur les recherches de ligne tardives pour améliorer les performances de LIMIT avec un grand décalage.

Mark Byers
la source
1
Cela ressemble plus à cela: P Bien que je désapprouve absolument l'implication, que les identifiants «plus récents» sont toujours plus grands que les identifiants «plus anciens», la plupart du temps ce sera effectivement le cas et donc, je pense, c'est «bon assez'. Quoi qu'il en soit, oui, comme vous l'avez démontré, la pagination appropriée (sans dégradation sévère des performances sur les grands ensembles de résultats) n'est pas particulièrement triviale et l'écriture limit 1000000, 10et espérer que cela fonctionnera ne vous mènera nulle part.
shylent
1
le lien de recherche tardive est très utile
pvgoddijn
1
Cette pagination fonctionne à l'envers si vous utilisez simplement "DESC" pour la commande des identifiants. Je l'aime!
Dennis Heiden
2
mais à quelle fréquence les gens veulent-ils commander par carte d'identité ou, par insinuation, par "date de création" dans le monde réel?
RichieHH
bon article, mais area=width*heightce n'est donc pas seulement la quantité d'enregistrements qui peut avoir de l'importance, mais la taille de chaque enregistrement est également un facteur lors du stockage des résultats en mémoire
rien n'est nécessaire
43

Définissez OFFSET pour la requête. Par exemple

page 1 - (enregistrements 01-10): décalage = 0, limite = 10;

page 2 - (enregistrements 11-20) offset = 10, limit = 10;

et utilisez la requête suivante:

SELECT column FROM table LIMIT {someLimit} OFFSET {someOffset};

exemple pour la page 2:

SELECT column FROM table
LIMIT 10 OFFSET 10;
Prabodh Hend
la source
1
Vous ne voulez pas dire offset = 10 pour la page-2?
Jenna Maiz
28

Il y a de la littérature à ce sujet:

Le problème principal se produit avec l'utilisation de grands OFFSETs. Ils évitent d'utiliser OFFSETavec une variété de techniques, allant des idsélections de plage dans la WHEREclause, à une sorte de pages de mise en cache ou de pré-calcul.

Il y a des solutions suggérées à Use the INDEX, Luke :

Luchostein
la source
1
obtenir l'id max pour chaque requête de pagination de requêtes complexes se traduirait par une utilisation non pratique et non éducative, le rang, le numéro de ligne et le type de clause de pagination entre les aides dans la performance!
Rizwan Patel
Cette stratégie est prise en considération et correctement évaluée dans les liens fournis. Ce n'est pas si simple du tout.
Luchostein
le lien fourni ne semble remplir que la mécanique de pivot de base uni-pivot, d'application croisée, multi CTE ou dérivée? encore une fois, je maintiens mon cas avec la réécriture de requêtes sur une telle ampleur pour obtenir maxid est une surpuissance architecturale! puis à nouveau permutation et combinaison pour n "nombre de colonnes avec des ordres de tri!
Rizwan Patel
1
Suis-je en train de mal comprendre que le lien "Pagination fait dans le bon sens", ou est-il simplement impossible dans toute requête impliquant un filtrage?
contactmatt
1
@contactmatt Je partage votre appréhension. En fin de compte, il semble qu'il n'y ait aucun moyen de mettre en œuvre efficacement l'exigence complète, mais des variations détendues autour de l'original.
Luchostein le
13

Ce tutoriel montre une excellente façon de faire de la pagination. Pagination efficace avec MySQL

En bref, évitez d'utiliser OFFSET ou large LIMIT

Bao Le
la source
24
peut-être donner un résumé?
Andrew
Ouais, j'apprécierais plus d'efforts dans la réponse.
Zorkind
6

tu peux aussi faire

SELECT SQL_CALC_FOUND_ROWS * FROM tbl limit 0, 20

Le nombre de lignes de l'instruction select (sans limite) est capturé dans la même instruction select afin que vous n'ayez pas à interroger à nouveau la taille de la table. Vous obtenez le nombre de lignes en utilisant SELECT FOUND_ROWS ();

surajz
la source
1
Ceci est particulièrement inefficace. Les *résultats dans plus de colonnes que nécessaire sont récupérés et les SQL_CALC_FOUND_ROWSrésultats dans ces colonnes sont lus à partir de toutes les lignes du tableau, même s'ils ne sont pas inclus dans le résultat. Il serait beaucoup plus efficace de calculer le nombre de lignes dans une requête distincte qui ne lit pas toutes ces colonnes. Ensuite, votre requête principale peut s'arrêter après avoir lu 20 lignes.
thomasrutter
Êtes-vous sûr? J'ai chronométré la requête sur une grande table SQL_CALC_FOUND_ROWS et une autre requête n'utilisant pas. Je n'ai vu aucun décalage horaire. De toute façon, c'est plus rapide que de faire 2 requêtes. 1 - sélectionnez * dans la limite de table 0 20, puis sélectionnez le nombre (*) dans la table.
surajz
1
Oui, je suis sûr - voici plus d'informations . Dans tous les cas, lorsque vous utilisez un index pour filtrer des lignes, SQL_CALC_FOUND_ROWS est beaucoup plus lent que d'effectuer 2 requêtes distinctes. À de rares occasions où vous n'utilisez pas d'index ou (comme dans cet exemple simplifié) vous n'avez pas de clause WHERE et c'est une table MYISAM, cela fait peu de différence (c'est à peu près à la même vitesse).
thomasrutter
Voici également une discussion à ce sujet sur Stackoverflow
thomasrutter
4

Requête 1: SELECT * FROM yourtable WHERE id > 0 ORDER BY id LIMIT 500

Requête 2: SELECT * FROM tbl LIMIT 0,500;

La requête 1 s'exécute plus rapidement avec des enregistrements petits ou moyens, si le nombre d'enregistrements est égal ou supérieur à 5 000, le résultat est similaire.

Résultat pour 500 enregistrements:

La requête 1 prend 9,9999904632568 millisecondes

La requête 2 prend 19,999980926514 millisecondes

Résultat pour 8 000 enregistrements:

La requête 1 prend 129,99987602234 millisecondes

Requête2: 160,00008583069 millisecondes

Huy
la source
Vous devez mettre un index id.
Maarten
6
Comment est id > 0utile?
Michel Jung
1
Comme l'a dit Maarten, ces deux requêtes semblent fondamentalement les mêmes et se décomposent probablement dans les mêmes commandes au niveau de la machine de toute façon. Vous devez avoir un problème d'indexation ou une version très ancienne de MySQL.
HoldOffHunger
merci, comme je n'ai pas vu votre réponse, j'avais juste besoin de voir l'ordre dans lequel où, l'ordre et la limite viennent
Shreyan Mehta
un mauvais exemple a été utilisé. avec offset(le premier argument à limiter est décalé), vous sélectionnez toujours toutes les données à la limite, puis vous supprimez cette quantité de décalage, puis vous retournez la section qui se situe entre offsetet limit. avec la whereclause d'autre part, vous définissez une sorte de point de départ pour la requête et interrogez ONLYcette partie spécifique.
senaps
0

La pagination est simple lorsqu'elle récupère des données à partir d'une seule table, mais elle est complexe lorsqu'elle récupère des données joignant plusieurs tables. Voici un bon exemple avec MySql et Spring:
https://www.easycodeforall.com/zpagination1.jsp

Susanta Ghosh
la source
Veuillez ne pas partager de liens vers des sites tiers qui pourraient un jour disparaître. Si vous cherchez à répondre à la question des auteurs, affichez le code approprié pour les aider.
Manchester sans marque le