Scénario en bref: une table avec plus de 16 millions d'enregistrements [taille de 2 Go]. Plus l'offset LIMIT est élevé avec SELECT, plus la requête ralentit lorsque vous utilisez ORDER BY * primary_key *
Alors
SELECT * FROM large ORDER BY `id` LIMIT 0, 30
prend beaucoup moins que
SELECT * FROM large ORDER BY `id` LIMIT 10000, 30
Cela ne commande que 30 disques et le même de toute façon. Donc, ce ne sont pas les frais généraux de ORDER BY.
Maintenant, pour récupérer les 30 dernières lignes, cela prend environ 180 secondes. Comment puis-je optimiser cette requête simple?
Il est normal que des décalages élevés ralentissent la requête, car celle-ci doit compter le premier OFFSET + LIMIT
enregistre (et n'en prend que LIMIT
). Plus cette valeur est élevée, plus la requête est longue.
La requête ne peut pas aller directement à OFFSET
parce que, d’une part, les enregistrements peuvent avoir une longueur différente, puis, qu’il peut y avoir des lacunes dans les enregistrements supprimés. Il doit vérifier et compter chaque enregistrement en chemin.
En supposant que id
soit un PRIMARY KEY
d'une table MyISAM
, vous pouvez l'accélérer en utilisant cette astuce:
SELECT t.*
FROM (
SELECT id
FROM mytable
ORDER BY
id
LIMIT 10000, 30
) q
JOIN mytable t
ON t.id = q.id
Voir cet article:
J'ai eu exactement le même problème moi-même. Étant donné que vous souhaitez collecter une grande quantité de ces données et non un ensemble spécifique de 30, vous exécuterez probablement une boucle et incrémenterez le décalage de 30.
Donc, ce que vous pouvez faire à la place est:
WHERE id > lastId limit 0,30
Ainsi, vous pouvez toujours avoir un décalage ZÉRO. Vous serez surpris par l'amélioration des performances.
MySQL ne peut pas aller directement au 10000ème enregistrement (ni au 80000ème octet, selon votre suggestion) car il ne peut pas supposer qu'il est emballé/ordonné de la sorte (ou qu'il a des valeurs continues comprises entre 1 et 10 000). Bien que cela puisse être le cas, MySQL ne peut pas supposer qu’il n’ya pas de trous/lacunes/ids supprimés.
Donc, comme noté dans les bobs, MySQL devra récupérer 10 000 lignes (ou parcourir les 10000e entrées de l'index sur id
) avant de trouver les 30 à retourner.
EDIT : Pour illustrer mon propos
Notez que bien
SELECT * FROM large ORDER BY id LIMIT 10000, 30
serait lent (er) ,
SELECT * FROM large WHERE id > 10000 ORDER BY id LIMIT 30
serait rapide (er) et renverrait les mêmes résultats à condition qu'il ne manque pas de id
s (c'est-à-dire des espaces vides).
J'ai trouvé un exemple intéressant d'optimiser les requêtes SELECT ORDER BY id LIMIT X, Y. J'ai 35 millions de lignes, il a donc fallu environ 2 minutes pour trouver une plage de lignes.
Voici le truc:
select id, name, address, phone
FROM customers
WHERE id > 990
ORDER BY id LIMIT 1000;
Il suffit de mettre le WHERE avec le dernier identifiant que vous avez obtenu pour augmenter considérablement les performances. Pour moi, c'était de 2 minutes à 1 seconde :)
Autres astuces intéressantes ici: http://www.iheavy.com/2013/06/19/3-ways-to-optimize-for-paging-in-mysql/
Ça marche aussi avec des cordes
La partie fastidieuse des deux requêtes consiste à extraire les lignes de la table. Logiquement, dans le LIMIT 0, 30
version, seules 30 lignes doivent être récupérées. Dans le LIMIT 10000, 30
version, 10000 lignes sont évaluées et 30 lignes sont renvoyées. Il peut y avoir une optimisation possible dans le processus de lecture des données, mais considérons les points suivants:
Et si vous aviez une clause WHERE dans les requêtes? Le moteur doit renvoyer toutes les lignes qualifiées, puis trier les données et obtenir les 30 lignes.
Considérez également le cas où les lignes ne sont pas traitées dans la séquence ORDER BY. Toutes les lignes qualifiantes doivent être triées pour déterminer les lignes à renvoyer.