Si vous concevez des applications ou des bases de donnĂ©es backend depuis un certain temps, vous avez probablement Ă©crit du code pour exĂ©cuter des requĂȘtes paginĂ©es. Par exemple - comme ceci:
SELECT * FROM table_name LIMIT 10 OFFSET 40
C'est comme ça?
Mais si c'est ainsi que vous avez fait la pagination, j'ai le regret de dire que vous ne l'avez pas fait de la maniĂšre la plus efficace.
Voulez-vous discuter avec moi? Vous n'avez pas à perdre de temps . Slack , Shopify et Mixmax utilisent déjà les astuces dont je veux parler aujourd'hui.
Nommez au moins un backend développeur, qui n'a jamais été utilisé
OFFSET
et LIMIT
pour effectuer des requĂȘtes avec pagination. Dans MVP (Minimum Viable Product, minimum viable product) et dans les projets qui utilisent de petites quantitĂ©s de donnĂ©es, cette approche est tout Ă fait applicable. Cela fonctionne, pour ainsi dire.
Mais si vous avez besoin de crĂ©er des systĂšmes fiables et efficaces Ă partir de zĂ©ro, vous devez vous assurer Ă l'avance de l'efficacitĂ© des requĂȘtes sur les bases de donnĂ©es utilisĂ©es dans ces systĂšmes.
Aujourd'hui, nous allons parler des problĂšmes associĂ©s aux implĂ©mentations largement utilisĂ©es (dĂ©solĂ©) de moteurs d'exĂ©cution de requĂȘtes paginĂ©es, et comment obtenir des performances Ă©levĂ©es lors de l'exĂ©cution de telles requĂȘtes.
Quel est le problĂšme avec OFFSET et LIMIT?
Comme il a été dit,
OFFSET
et se LIMIT
montre parfaitement dans des projets qui n'ont pas besoin de travailler avec de grandes quantités de données.
Le problĂšme survient lorsque la base de donnĂ©es atteint une taille telle qu'elle cesse de tenir dans la mĂ©moire du serveur. Cependant, lorsque vous travaillez avec cette base de donnĂ©es, vous devez utiliser des requĂȘtes paginĂ©es.
Pour que ce problĂšme se manifeste, il est nĂ©cessaire qu'une situation se produise dans laquelle le SGBD recourt Ă une opĂ©ration d'analyse complĂšte de la table inefficace lors de l'exĂ©cution de chaque requĂȘte avec pagination (en mĂȘme temps, des opĂ©rations d'insertion et de suppression de donnĂ©es peuvent se produire , et nous n'avons pas besoin de donnĂ©es obsolĂštes!).
Qu'est-ce qu'un "balayage complet de la table" (ou "balayage sĂ©quentiel de la table", balayage sĂ©quentiel)? Il s'agit d'une opĂ©ration au cours de laquelle le SGBD lit sĂ©quentiellement chaque ligne de la table, c'est-Ă -dire les donnĂ©es qu'elle contient, et les vĂ©rifie par rapport Ă une condition donnĂ©e. Ce type d'analyse de table est connu pour ĂȘtre le plus lent. Le fait est que lors de son exĂ©cution, de nombreuses opĂ©rations d'E / S sont effectuĂ©es en utilisant le sous-systĂšme de disque du serveur. La situation est aggravĂ©e par les retards associĂ©s au travail avec des donnĂ©es stockĂ©es sur des disques et par le fait que le transfert de donnĂ©es du disque vers la mĂ©moire est une opĂ©ration gourmande en ressources.
Par exemple, vous avez des enregistrements de 100 000 000 d'utilisateurs et vous exĂ©cutez une requĂȘte avec la construction
OFFSET 50000000
... Cela signifie que le SGBD devra charger tous ces enregistrements (et nous n'en avons mĂȘme pas besoin!), Les placer en mĂ©moire, et seulement aprĂšs cela, prenez, disons, 20 rĂ©sultats rapportĂ©s LIMIT
.
Supposons que cela ressemble Ă "sĂ©lectionnez les lignes 50 000 Ă 500 20 sur 100 000". Autrement dit, le systĂšme devra d'abord charger 50 000 lignes pour exĂ©cuter la requĂȘte. Vous voyez combien de travail inutile elle doit faire?
Si vous ne me croyez pas, jetez un Ćil Ă l'exemple que j'ai crĂ©Ă© en utilisant db-fiddle.com .
Exemple sur db-fiddle.com
LĂ , Ă gauche, dans la boĂźte
Schema SQL
, il y a du code pour insérer 100 000 lignes dans la base de données, et à droite, dans la boßteQuery SQL
, deux requĂȘtes sont affichĂ©es. Le premier, lent, ressemble Ă ceci:
SELECT *
FROM `docs`
LIMIT 10 OFFSET 85000;
Et le second, qui est une solution efficace au mĂȘme problĂšme, comme ceci:
SELECT *
FROM `docs`
WHERE id > 85000
LIMIT 10;
Afin de répondre à ces demandes, il vous suffit de cliquer sur le bouton
Run
en haut de la page. Cela fait, comparons les informations sur le temps d'exĂ©cution de la requĂȘte. Il s'avĂšre que l'exĂ©cution d'une requĂȘte inefficace prend au moins 30 fois plus de temps que l'exĂ©cution de la seconde (cette durĂ©e diffĂšre d'un lancement Ă l'autre, par exemple, le systĂšme peut signaler que la premiĂšre requĂȘte a pris 37 ms pour se terminer, et exĂ©cution de la seconde - 1 ms).
Et s'il y a plus de donnĂ©es, alors tout sera encore pire (pour en ĂȘtre convaincu - jetez un Ćil Ă mon exemple avec 10 millions de lignes).
Ce que nous venons de discuter devrait vous donner un aperçu de la maniĂšre dont les requĂȘtes de base de donnĂ©es sont rĂ©ellement traitĂ©es.
Gardez à l'esprit que plus la valeur est élevée
OFFSET
- plus la demande prendra de temps.
Que faut-il utiliser Ă la place d'une combinaison de OFFSET et LIMIT?
Au lieu d'une combinaison
OFFSET
, LIMIT
il vaut la peine d'utiliser une structure construite selon le schéma suivant:
SELECT * FROM table_name WHERE id > 10 LIMIT 20
Il s'agit de l'exĂ©cution d'une requĂȘte de pagination basĂ©e sur le curseur.
Au lieu du courant stocké localement
OFFSET
et LIMIT
et les envoyer à chaque demande, il est nécessaire de stocker la derniÚre clé primaire reçue ( en général - a ID
) et LIMIT
, en conséquence et sera invité ressemblant au - dessus-indiqué.
Pourquoi? Le fait est qu'en spĂ©cifiant explicitement l'identifiant de la derniĂšre ligne lue, vous indiquez Ă votre SGBD oĂč il doit commencer Ă rechercher les donnĂ©es dont il a besoin. De plus, la recherche, grĂące Ă l'utilisation de la clĂ©, sera effectuĂ©e de maniĂšre efficace, le systĂšme n'aura pas Ă ĂȘtre distrait par des lignes qui sont en dehors de la plage spĂ©cifiĂ©e.
Jetons un coup d'Ćil Ă la comparaison de performances suivante de diffĂ©rentes requĂȘtes. Voici une requĂȘte inefficace.
RequĂȘte lente
Et voici une version optimisĂ©e de cette requĂȘte.
RequĂȘte rapide
Les deux requĂȘtes renvoient exactement la mĂȘme quantitĂ© de donnĂ©es. Mais le premier prend 12,80 secondes et le second, 0,01 seconde. Sentez-vous la diffĂ©rence?
ProblĂšmes possibles
Pour que la mĂ©thode d'exĂ©cution de requĂȘte proposĂ©e fonctionne efficacement, la table doit avoir une colonne (ou des colonnes) contenant des index sĂ©quentiels uniques, tels qu'un identifiant entier. Dans certains cas spĂ©cifiques, cela peut dĂ©terminer le succĂšs de l'utilisation de telles requĂȘtes afin d'augmenter la vitesse de travail avec la base de donnĂ©es.
Naturellement, lors de la conception de requĂȘtes, vous devez prendre en compte les particularitĂ©s de l'architecture des tables, et choisir les mĂ©canismes qui se montreront le mieux sur les tables existantes. Par exemple, si vous devez travailler dans des requĂȘtes avec de grandes quantitĂ©s de donnĂ©es associĂ©es, vous pourriez trouver cet article intĂ©ressant .
Si nous sommes confrontés au problÚme de l'absence de clé primaire, par exemple, s'il existe une table avec une relation plusieurs-à -plusieurs, alors l'approche traditionnelle consistant à utiliser
OFFSET
et LIMIT
est garantie de fonctionner pour nous. Mais son application peut conduire Ă l'exĂ©cution de requĂȘtes potentiellement lentes. Dans de tels cas, je recommanderais d'utiliser une clĂ© primaire Ă auto-incrĂ©mentation, mĂȘme si vous n'en avez besoin que pour organiser les requĂȘtes paginĂ©es.
Si vous ĂȘtes intĂ©ressĂ© par ce sujet - ici , ici et ici - quelques matĂ©riaux utiles.
RĂ©sultat
La principale conclusion que nous pouvons tirer est que toujours, quelle que soit la taille des bases de donnĂ©es dont nous parlons, nous devons analyser la vitesse d'exĂ©cution des requĂȘtes. Ă notre Ă©poque, l'Ă©volutivitĂ© des solutions est extrĂȘmement importante, et si vous concevez tout correctement dĂšs le dĂ©but du travail sur un certain systĂšme, cela, Ă l'avenir, peut Ă©viter au dĂ©veloppeur de nombreux problĂšmes.
Comment analysez-vous et optimisez-vous les requĂȘtes de base de donnĂ©es?