N'utilisez pas OFFSET et LIMIT dans les requĂȘtes paginĂ©es

Il est rĂ©volu le temps oĂč vous n'aviez pas Ă  vous soucier de l'optimisation des performances de la base de donnĂ©es. Le temps ne s'arrĂȘte pas. Chaque nouvel entrepreneur en technologie souhaite crĂ©er un autre Facebook tout en s'efforçant de collecter toutes les donnĂ©es qu'il peut atteindre. Les entreprises ont besoin de ces donnĂ©es pour une meilleure formation des modĂšles qui aident Ă  gagner de l'argent. Dans de telles conditions, les programmeurs doivent crĂ©er des API qui leur permettent de travailler rapidement et de maniĂšre fiable avec d'Ă©normes quantitĂ©s d'informations.







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Ă© OFFSETet LIMITpour 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, OFFSETet se LIMITmontre 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 constructionOFFSET 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ĂźteSchema 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 Runen 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éeOFFSET - 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, LIMITil 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 OFFSETet LIMITet 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 OFFSETet LIMITest 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?






All Articles