Utiliser will_paginate without: total_entries pour améliorer une requête longue
-
07-07-2019 - |
Question
J'ai actuellement une implémentation de will_paginate qui utilise la méthode paginate_by_sql pour générer la collection à paginer. Nous avons une requête personnalisée pour total_entries qui est très compliquée et alourdit considérablement notre base de données. Par conséquent, nous aimerions couper le nombre total d'entrées de la pagination.
En d’autres termes, au lieu de l’affichage de pagination typique de «précédent 1 [2] 3 4 5 suivant», nous aimerions simplement un bouton «suivant - précédent». Mais nous devons savoir quelques choses.
- Affiche-t-on le lien précédent? Ceci ne se produira évidemment que si les enregistrements existants avant ceux affichés dans la sélection actuelle
- Affiche-t-on le lien suivant? Ceci ne sera pas affiché si le dernier enregistrement de la collection est affiché
Une requête pour compter les lignes sera être généré automatiquement si vous ne pas fournir: total_entries. Si vous rencontrer des problèmes avec cette SQL généré, vous voudrez peut-être effectuer le compte manuellement dans votre application.
La situation idéale est donc la suivante.
- Supprimez le nombre total d'entrées car cela entraîne une charge excessive sur la base de données
- Afficher 50 enregistrements à la fois avec une semi-pagination en utilisant uniquement les boutons suivants / précédents pour naviguer et ne nécessitant pas l'affichage de tous les numéros de page disponibles
- Afficher uniquement le bouton suivant et le bouton précédent en conséquence
Quelqu'un a-t-il déjà travaillé sur un problème similaire ou a-t-il une solution au problème?
La solution
Il y a de nombreuses occasions où will_paginate calcule vraiment le nombre d'entrées, en particulier si des jointures impliquent de confondre le générateur de décompte SQL.
Si tout ce dont vous avez besoin est une simple méthode prev / next, il vous suffit d'essayer d'extraire N + 1 entrées de la base de données, et si vous n'obtenez que N ou moins que ce que vous êtes à la dernière page.
Par exemple:
per_page = 10
page = 2
@entries = Thing.with_some_scope.find(:all, :limit => per_page + 1, :offset => (page - 1) * per_page)
@next_page = @entries.slice!(per_page, 1)
@prev_page = page > 1
Vous pouvez facilement encapsuler ceci dans un module pouvant être inclus dans les différents modèles qui le nécessitent ou créer une extension de contrôleur.
J'ai constaté que cela fonctionnait nettement mieux que la méthode will_paginate par défaut.
Le seul problème de performances est une limitation de MySQL qui peut poser problème en fonction de la taille de vos tables.
Pour quelque raison que ce soit, le temps nécessaire pour effectuer une requête avec une petite limite dans MySQL est proportionnel à OFFSET. En effet, le moteur de base de données lit toutes les lignes menant à la valeur de décalage particulière, puis renvoie les lignes suivantes du nombre LIMIT, sans les avancer comme prévu.
Pour les grands ensembles de données, où les valeurs OFFSET se situent dans la plage plus de 100 000, il est possible que les performances se dégradent considérablement. Cela va se traduire par le fait que le chargement de la page 1 est très rapide, que la page 1000 est un peu lente, mais que la page 2000 est extrêmement lente.