L'utilisation de LIMIT améliore-t-elle les performances et est-elle perceptible?

11

Je veux comprendre ce qui suit.
Supposons que j'ai une requête compliquée avec disons une jointure de 5 tables par groupe par sommations et par ordre.
Abstraction faite de toutes les optimisations à la requête elle - même , par exemple des index , etc.
Y at - il avantage de performance significative en utilisant LIMIT? Je suppose que toutes les requêtes (et les résultats) doivent être traitées avant d' appliquer LIMIT, donc l'utilisation d'un LIMIT pour récupérer un sous-ensemble des résultats, cela offre-t-il une amélioration significative / perceptible?

Jim
la source
2
Je vous suggère de lire ceci, pour les cas qui LIMITaméliorent l'efficacité: Optimisation des requêtes LIMIT
ypercubeᵀᴹ

Réponses:

10

Si vous souhaitez profiter de LIMITpour améliorer les performances, vous avez besoin

  • comprendre les données que vous récupérez
  • indexation correcte de la bonne séquence de colonnes
  • prendre la responsabilité de refactoriser la requête
  • utiliser LIMITavantJOIN

Ces principes peuvent aller très loin si vous pouvez les orchestrer.

J'ai appris ces concepts en regardant cette vidéo YouTube (écoutez attentivement à travers l'accent français)

J'ai utilisé ces concepts pour répondre à une question StackOverflow très difficile sur l'obtention des 40 meilleurs articles de certaines tables: 12 mai 2011: récupération d'une seule ligne à partir de la table de jointure .

Dans ma réponse à cette question (16 mai 2011) , j'ai écrit la requête suivante et l'ai soigneusement testée:

SELECT
  AAA.author_id,
  AAA.date_created,
  IFNULL(BBB.title,'<NO_TITLE>') title,
  IFNULL(CCC.filename,'<NO-IMAGE>') filename,
  IFNULL(CCC.date_added,'<NO-IMAGE-DATE>') image_date
FROM
(
  SELECT
    AA.id,
    AA.date_added,
    BB.author_id,
    BB.date_created
  FROM
  (
    SELECT
      A.id,IFNULL(MAX(B.date_added),'1900-01-01 00:00:00') date_added
      FROM (SELECT id FROM articles ORDER BY date_created DESC LIMIT 40) A
      LEFT JOIN article_images B ON A.id = B.article_id
      GROUP BY A.id
  ) AA
  INNER JOIN articles BB USING (id)
) AAA
LEFT JOIN article_contents BBB ON AAA.id=BBB.article_id
LEFT JOIN article_images CCC
ON (AAA.id=CCC.article_id AND AAA.date_added=CCC.date_added)
ORDER BY AAA.date_created DESC;

Veuillez noter la ligne dans la requête avec le LIMIT

      FROM (SELECT id FROM articles ORDER BY date_created DESC LIMIT 40) A

Cette sous-requête est enfouie à trois niveaux de profondeur. Cela m'a permis d'utiliser les 40 derniers articles LIMIT. Ensuite, j'ai effectué les JOIN nécessaires par la suite.

LEÇONS APPRISES

  • Faire des LIMITsous-requêtes internes peut ne pas toujours être la réponse en raison de la cardinalité des index, du contenu des données et de la taille du jeu de résultats à partir du LIMIT. Si vous avez tous vos «canards d'affilée» (ayez les quatre principes à l'esprit pour votre requête), vous pouvez obtenir des résultats étonnamment bons.
  • Faites vos requêtes aussi simplistes que possible lorsque vous le faites LIMITen rassemblant uniquement les clés.
RolandoMySQLDBA
la source
(A [LEFT] JOIN B) LIMIT 100Est donc équivalent à (A LIMIT 100) [LEFT] JOIN (B LIMIT 100)? Où [LEFT] JOINsignifie jointure externe ou interne
Jim
C'est plus comme (A LIMIT 100) [LEFT] JOIN B. L'idée est d'utiliser LIMITpour déterminer la taille de l'ensemble de résultats le plus tôt possible. J'utilise également LEFT JOINau lieu de INNER JOINcar LEFT JOINpréservera l'ordre des touches sur le côté gauche.
RolandoMySQLDBA
@Jim Non, ils ne le sont pas. Parfois, ils sont, comme celui-ci: (A LEFT JOIN B) GROUP BY A.pk LIMIT 100peuvent généralement être réécrits comme (A LIMIT 100) LEFT JOIN B GROUP BY A.pk(pas de jointure interne ici, avec des jointures internes, ils ne seraient pas équivalents.) L'exemple de Rolando est exactement un tel cas.
ypercubeᵀᴹ
@ypercube: Donc, avec les jointures internes, n'y a-t-il rien à faire pour bénéficier de LIMIT?
Jim
Je faisais référence à la stratégie de réécriture décrite par Rolando. Une requête avec JOINs et LIMIT peut également bénéficier. Ou pas. Ça dépend.
ypercubeᵀᴹ
2

Lorsqu'une requête est exécutée, elle est d'abord traduite en un plan composé de plusieurs opérateurs. Il existe deux types d'opérateurs de base: bloquant et non bloquant. Un opérateur non bloquant récupère une ligne (ou quelques lignes) de son ou ses enfants pour chaque ligne demandée. Un opérateur de blocage, d'autre part, doit lire et traiter l'ensemble de lignes de tous ses enfants avant de pouvoir produire une sortie.

Le tri est un opérateur de blocage typique. Ainsi, une sélection avec commande par ne bénéficie pas beaucoup d'une limite. Cependant, il existe des SGBDR qui peuvent utiliser un algorithme de tri qui nécessite moins de mémoire et est plus rapide lorsqu'une clause limite est fournie. Dans ce cas, il suffit de stocker les n premières lignes actuellement et de les retirer de la mémoire au fur et à mesure que les lignes précédentes se présentent. Cela peut être un gain de performances significatif. Cependant, je ne suis pas sûr à 100% que MySQL ait cette capacité.

Quoi qu'il en soit, même un tri par limite doit toujours traiter l'ensemble de lignes d'entrée avant de pouvoir produire la première ligne de sortie. Bien que cet algorithme, s'il est implémenté, puisse accélérer le tri, si le reste de la requête est la partie la plus coûteuse, le temps d'exécution total ne s'améliorera pas de manière significative en raison d'une limite fournie.

Sebastian Meine
la source
Je suis peu confus avec la réponse. Vous mentionnez le tri, mais le tri par le trie également, Donc, par exemple, si j'ai supprimé la commande avant de rester avec le groupe avant, votre réponse s'applique-t-elle toujours? Ou une analyse différente est-elle nécessaire?
Jim
Selon la requête et les index présents, cela GROUP BYpourrait potentiellement conduire à un plan qui ne contient pas d'opérateurs de blocage.
Sebastian Meine
0

Dans mon cas, je peux dire Oui , même si je ne comprends toujours pas pourquoi.

SELECT g0_.id AS id_0, COUNT(a1_.id_tarifs) AS sclr_1
FROM groupe_jardinerie g0_
INNER JOIN articles_tarifs a1_
  ON (a1_.groupe_jardinerie_id = g0_.id)
WHERE g0_.centrale_id = 511
  AND a1_.date_fin_tarif >= '2018-01-29 10:46:35'
GROUP BY g0_.id;

(result set)

8 rows in set (**18.14 sec**)

Notez le temps: 18 secondes. Même demande avec une grosse LIMITE:

SELECT g0_.id AS id_0, COUNT(a1_.id_tarifs) AS sclr_1 
FROM groupe_jardinerie g0_
INNER JOIN articles_tarifs a1_
  ON (a1_.groupe_jardinerie_id = g0_.id)
WHERE g0_.centrale_id = 511 
  AND a1_.date_fin_tarif >= '2018-01-29 10:46:35'
GROUP BY g0_.id
LIMIT 100000000000;

(exact same result set)

8 rows in set (**1.32 sec**)

Plus de dix fois plus rapide !!!

EXPLAIN donne le même résultat pour les deux requêtes.

+----+-------------+-------+------------+--------+---------------------------------------------------+---------+---------+------------------------------+--------+----------+----------------------------------------------+
| id | select_type | table | partitions | type   | possible_keys                                     | key     | key_len | ref                          | rows   | filtered | Extra                                        |
+----+-------------+-------+------------+--------+---------------------------------------------------+---------+---------+------------------------------+--------+----------+----------------------------------------------+
|  1 | SIMPLE      | a1_   | NULL       | ALL    | IDX_438010BBC10784EF                              | NULL    | NULL    | NULL                         | 795135 |    33.33 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | g0_   | NULL       | eq_ref | PRIMARY,IDX_9CA5CF6758A1D71F,IDX_9CA5CF67670C757F | PRIMARY | 4       | phs.a1_.groupe_jardinerie_id |      1 |    50.00 | Using where                                  |
+----+-------------+-------+------------+--------+---------------------------------------------------+---------+---------+------------------------------+--------+----------+----------------------------------------------+

LIMIT ne devrait interférer que pour limiter le jeu de résultats (c'est-à-dire que si je fais un LIMIT 4, je n'ai que les 4 premières lignes du jeu de résultats ci-dessus).

Pierre-Olivier Vares
la source
terrifiant, quelle version utilisez-vous et pouvez-vous créer un cas de test simplifié?
Evan Carroll
1
Votre réponse ne prouve aucun nouvel avantage pour LIMIT. Votre première requête s'exécute en 18 secondes, donnant un ensemble de résultats. Toutes les données de la 2e requête sont déjà mises en cache dans le pool de mémoire tampon InnoDB en raison de la première requête, donc bien sûr la 2e requête doit être plus rapide, même si vous redémarrez mysql, exécutez la 1ère requête, redémarrez mysql et exécutez la 2e requête, vous obtiendrez le même résultat. . Avoir un meilleur résultat pour LIMITne peut venir que de faire: 1) LIMITavant JOIN, 2) LIMIT dans l'ordre de tri ASCou DESC.
RolandoMySQLDBA
Merci de votre intérêt, la création d'un scénario de test simplifié peut être difficile.
Pierre-Olivier Vares