Bonjour à tous ! Je suis développeur backend, je crée des microservices en Java + Spring. Je travaille dans une des équipes de développement de produits internes chez Tinkoff.

Dans notre Ă©quipe, la question de l'optimisation des requĂȘtes dans les SGBD se pose souvent. On souhaite toujours un peu plus de rapiditĂ©, mais il n'est pas toujours possible de se contenter d'indices bien construits â il faut parfois chercher des solutions alternatives. Lors de l'une de mes explorations sur le web Ă la recherche d'optimisations raisonnables pour travailler avec des bases de donnĂ©es, j'ai trouvĂ© , auteur du livre SQL Performance Explained. C'est le genre de blog rare oĂč l'on peut lire tous les articles Ă la suite.
Je souhaite traduire pour vous un petit article de Markus. On peut l'appeler dans une certaine mesure un manifeste, qui vise à attirer l'attention sur un problÚme ancien, mais toujours d'actualité, concernant la performance des opérations offset selon la norme SQL.
Dans certains endroits, je compléterai l'auteur avec des explications et des remarques. Tous ces endroits seront marqués comme « remarque » pour plus de clarté.
Introduction rapide
Je pense que beaucoup savent Ă quel point il peut ĂȘtre problĂ©matique et lent de travailler avec des sĂ©lections paginĂ©es via offset. Mais saviez-vous qu'il est assez simple de le remplacer par une construction plus performante ?
Ainsi, le mot clĂ© offset indique Ă la base de donnĂ©es de passer les n premiĂšres entrĂ©es dans la requĂȘte. Cependant, la base doit toujours lire ces n premiĂšres entrĂ©es sur le disque, et ce dans l'ordre spĂ©cifiĂ© (remarque : appliquer le tri si dĂ©fini), et seulement aprĂšs cela, il sera possible de retourner les entrĂ©es Ă partir de n + 1 et au-delĂ . Ce qui est intĂ©ressant, c'est que le problĂšme ne rĂ©side pas dans la mise en Ćuvre spĂ©cifique du SGBD, mais dans la dĂ©finition initiale selon la norme :
âŠles lignes sont d'abord triĂ©es selon la <clause order by> puis limitĂ©es en abandonnant le nombre de lignes spĂ©cifiĂ© dans la <clause result offset> depuis le dĂ©butâŠ
-SQL:2016, Partie 2, 4.15.3 Tables dérivées (remarque : actuellement la norme la plus utilisée)
Le point clĂ© ici est que l'offset prend un seul paramĂštre â le nombre d'enregistrements Ă ignorer, et c'est tout. En suivant cette dĂ©finition, le SGBD ne peut que rĂ©cupĂ©rer tous les enregistrements, puis abandonner les inutiles. Ăvidemment, une telle dĂ©finition de l'offset oblige Ă effectuer un travail supplĂ©mentaire. Et il n'est mĂȘme pas important qu'il s'agisse de SQL ou de NoSQL.
Encore un peu de douleur
Les problĂšmes d'offset ne s'arrĂȘtent pas lĂ , et voilĂ pourquoi. Si une nouvelle entrĂ©e est insĂ©rĂ©e entre la lecture de deux pages de donnĂ©es Ă partir du disque, que se passe-t-il dans ce cas ?

Lorsque l'offset est utilisé pour sauter des enregistrements des pages précédentes, dans le cas d'une insertion d'un nouvel enregistrement entre les opérations de lecture de pages différentes, vous obtiendrez trÚs probablement des doublons (note : cela est possible lorsque nous lisons page par page en utilisant la clause order by, alors un nouvel enregistrement peut entrer en milieu de notre résultat).
L'illustration montre clairement cette situation. La base lit les 10 premiers enregistrements, aprÚs quoi un nouvel enregistrement est inséré, décalant tous les enregistrements lus d'un chiffre. Ensuite, la base récupÚre une nouvelle page des 10 enregistrements suivants et commence non pas à la 11e, comme elle le devrait, mais à la 10e, dupliquant cet enregistrement. Il existe d'autres anomalies liées à l'utilisation de cette expression, mais celle-ci est la plus courante.
Comme nous l'avons dĂ©jĂ dĂ©terminĂ©, ce n'est pas un problĂšme d'un SGBD spĂ©cifique ou de ses implĂ©mentations. Le problĂšme rĂ©side dans la dĂ©finition de la pagination selon le standard SQL. Nous disons au SGBD quelle page rĂ©cupĂ©rer ou combien d'enregistrements ignorer. La base ne peut tout simplement pas optimiser cette requĂȘte, car il n'y a pas assez d'informations Ă ce sujet.
Il convient Ă©galement de prĂ©ciser que ce n'est pas un problĂšme liĂ© Ă un mot-clĂ© spĂ©cifique, mais plutĂŽt Ă la sĂ©mantique de la requĂȘte. Il existe encore plusieurs syntaxes identiques en termes de problĂšme :
- Le mot-clé offset, comme mentionné précédemment.
- La construction composée de deux mots-clés limit [offset] (bien que limit en soi ne soit pas si mauvais).
- Filtrage par les bornes inférieures, basé sur la numérotation des lignes (par exemple, row_number(), rownum, etc.).
Toutes ces expressions disent simplement combien de lignes doivent ĂȘtre ignorĂ©es, sans aucune information ou contexte supplĂ©mentaire.
Plus loin dans cet article, le mot-clé offset est utilisé comme une généralisation de toutes ces variantes.
Une vie sans OFFSET
Imaginez maintenant à quoi ressemblerait notre monde sans tous ces problÚmes. Il s'avÚre que vivre sans offset n'est pas si compliqué : nous pouvons sélectionner uniquement les lignes que nous n'avons pas encore vues (note : c'est-à -dire celles qui n'étaient pas sur la page précédente), en utilisant une condition dans le where.
Dans ce cas, nous partons du fait que les sĂ©lections sont exĂ©cutĂ©es sur un ensemble ordonnĂ© (le bon vieux order by). Ătant donnĂ© que nous avons un ensemble ordonnĂ©, nous pouvons utiliser un filtre assez simple pour ne rĂ©cupĂ©rer que les donnĂ©es qui se trouvent aprĂšs le dernier enregistrement de la page prĂ©cĂ©dente :
SELECT ...
FROM ...
WHERE ...
AND id < ?last_seen_id
ORDER BY id DESC
FETCH FIRST 10 ROWS ONLYEt voilĂ le principe de cette approche. Bien sĂ»r, lorsque vous triez par plusieurs colonnes, cela devient plus intĂ©ressant, mais l'idĂ©e reste la mĂȘme. Il est important de noter que cette construction est applicable dans de nombreux -solutions.
Cette approche s'appelle la mĂ©thode seek ou pagination par jeu de clĂ©s. Elle rĂ©sout le problĂšme des rĂ©sultats flottants (note : la situation avec les enregistrements entre les lectures de pages, dĂ©crite prĂ©cĂ©demment) et, bien sĂ»r, ce que nous aimons tous, elle fonctionne plus rapidement et de maniĂšre plus stable que l'offset classique. La stabilitĂ© rĂ©side dans le fait que le temps de traitement de la requĂȘte n'augmente pas proportionnellement au numĂ©ro de la table demandĂ©e (note : si vous souhaitez en savoir plus sur le fonctionnement des diffĂ©rentes approches de pagination, vous pouvez . Vous y trouverez Ă©galement des benchmarks comparatifs sur les diffĂ©rentes mĂ©thodes).
Une des diapositives , la pagination par clĂ©s n'est bien sĂ»r pas omnipotente â elle a ses limites. La plus significative est qu'elle n'a pas la capacitĂ© de lire des pages alĂ©atoires (note : non sĂ©quentiellement). Cependant, Ă l'Ăšre du dĂ©filement infini (note : sur le front-end), ce n'est pas un si gros problĂšme. Indiquer le numĂ©ro de page pour un clic est de toute façon une mauvaise solution lors de la conception de l'UI (note : c'est l'avis de l'auteur de l'article).
Et qu'en est-il des outils ?
La pagination par clés ne convient souvent pas en raison d'un manque de soutien instrumental pour cette méthode. La plupart des outils de développement, y compris divers frameworks, ne permettent pas de choisir la méthode de pagination qui sera utilisée.
La situation est aggravĂ©e par le fait que la mĂ©thode dĂ©crite nĂ©cessite un soutien continu dans les technologies utilisĂ©es â allant de la base de donnĂ©es Ă l'exĂ©cution des requĂȘtes AJAX dans le navigateur lors du dĂ©filement infini. Au lieu d'indiquer uniquement le numĂ©ro de page, il faudra maintenant spĂ©cifier un ensemble de clĂ©s pour toutes les pages Ă la fois.
Cependant, le nombre de frameworks prenant en charge la pagination par clés augmente progressivement. Voici ce qui existe à ce jour :
- pour Java ;
- pour Ruby ;
- et pour Django ;
- pour Python ;
- â API de critĂšres pour les implĂ©mentations JPA ;
- pour Perl ;
- , ĐŒĐ°ĐżĐ”Ń ĐŽĐ»Ń Node.js .
(Remarque : certains liens ont Ă©tĂ© retirĂ©s car, au moment de la traduction, certaines bibliothĂšques n'avaient pas Ă©tĂ© mises Ă jour depuis 2017â2018. Si cela vous intĂ©resse, vous pouvez consulter la source.)
C'est précisément à ce stade que votre aide est nécessaire. Si vous développez ou maintenez un framework qui utilise d'une maniÚre ou d'une autre la pagination, je vous demande, je vous implore de fournir un support natif pour la pagination par clés. Si vous avez des questions ou avez besoin d'aide, je serais ravi de vous aider (, , ) (remarque : d'aprÚs mon expérience avec Markus, je peux dire qu'il est vraiment enthousiaste à l'idée de promouvoir ce sujet).
Si vous utilisez des solutions prĂȘtes Ă l'emploi qui, selon vous, mĂ©ritent un support de pagination par clĂ©s, n'hĂ©sitez pas Ă crĂ©er une demande ou mĂȘme Ă proposer une solution prĂȘte Ă l'emploi, si possible. Vous pouvez Ă©galement faire rĂ©fĂ©rence Ă cet article dans le lien.
Conclusion
La raison pour laquelle un approche aussi simple et utile que la pagination par clĂ©s est peu rĂ©pandue n'est pas qu'elle est difficile Ă mettre en Ćuvre techniquement ou nĂ©cessite des efforts considĂ©rables. La principale raison est que beaucoup de gens sont habituĂ©s Ă voir et Ă travailler avec l'offset â cette approche est dictĂ©e par la norme elle-mĂȘme.
En consĂ©quence, peu de personnes envisagent de changer leur approche de la pagination, ce qui rend Ă©galement le soutien par les frameworks et bibliothĂšques peu dĂ©veloppĂ©s. Donc, si vous ĂȘtes en phase avec l'idĂ©e et l'objectif de la pagination sans offset, aidez Ă la promouvoir !
Source :
Auteur : Markus Winand
Source : habr.com
