web-dev-qa-db-fra.com

Performances MySQL pour la version 5.7 contre 5.6

J'ai remarqué un problème de performances particulier pour lequel je ne suis pas sûr de savoir comment traiter.

Je suis en train de migrer une application Web d'un serveur à un autre avec des spécifications très similaires. Le nouveau serveur est généralement plus performant que l'ancien.

L'ancien serveur exécute MySQL 5.6.35 
Le nouveau serveur exécute MySQL 5.7.17

Le nouveau et l'ancien serveur ont des configurations MySQL pratiquement identiques . Le nouveau et l'ancien serveur exécutent la même base de données, parfaitement dupliquée.

L'application Web en question est Magento 1.9.3.2.

Dans Magento, la fonction suivanteMage_Catalog_Model_Category::getChildrenCategories() est destinée à répertorier toutes les catégories d'enfants immédiates pour une catégorie donnée.

Dans mon cas, cette fonction bouillonne finalement vers cette requête:

SELECT    `main_table`.`entity_id`
        , main_table.`name`
        , main_table.`path`
        , `main_table`.`is_active`
        , `main_table`.`is_anchor`
        , `url_rewrite`.`request_path`

FROM `catalog_category_flat_store_1` AS `main_table`

LEFT JOIN `core_url_rewrite` AS `url_rewrite`
ON url_rewrite.category_id=main_table.entity_id
AND url_rewrite.is_system=1
AND url_rewrite.store_id = 1
AND url_rewrite.id_path LIKE 'category/%'

WHERE (main_table.include_in_menu = '1')
AND (main_table.is_active = '1')
AND (main_table.path LIKE '1/494/%')
AND (`level` <= 2)
ORDER BY `main_table`.`position` ASC;

Bien que la structure de cette requête soit la même pour toutes les installations Magento, il y aura évidemment de légères incohérences entre les valeurs d'installation de Magento à Installation de Magento et la catégorie dans laquelle la fonction s'intéresse.

Ma table catalog_category_flat_store_1 a 214 lignes. 
Ma table url_rewrite contient 1 734 316 lignes.

Cette requête, lorsqu'elle est exécutée seule directement dans MySQL, fonctionne très différemment d'une version à l'autre de MySQL.

J'utilise SQLyog pour profiler cette requête.

Dans MySQL 5.6, la requête ci-dessus est exécutée en 0,04 seconde. Le profil de cette requête ressemble à ceci: https://codepen.io/Petce/full/JNKEpy/

Dans MySQL 5.7, la requête ci-dessus est exécutée en 1,952 secondes. Le profil de cette requête ressemble à ceci: https://codepen.io/Petce/full/gWMgKZ/

Comme vous pouvez le constater, la même requête sur presque la même configuration est pratiquement 2 secondes plus lente, et je ne suis pas sûr de savoir pourquoi.

Pour une raison quelconque, MySQL 5.7 ne souhaite pas utiliser l'index de table pour générer le jeu de résultats.

Toute personne disposant de plus d'expérience/de connaissances peut expliquer ce qui se passe ici et comment s'y prendre pour la réparer?

Je pense que le problème a quelque chose à voir avec le fonctionnement de l'optimiseur MYSQL 5.7. Pour une raison quelconque, il semble penser qu'une analyse complète de la table est la voie à suivre. Je peux améliorer considérablement les performances de la requête en définissant max_seeks_for_key sur low (par exemple, 100) ou en supprimant la valeur range_optimizer_max_mem_size afin de la forcer à envoyer un avertissement.

Effectuer l’une ou l’autre de ces opérations augmente la vitesse de la requête de près de 10 fois à 0,2 seconde. Toutefois, cela est toujours plus lent que MYSQL 5.6, qui s’exécute en 0,04 seconde, et je ne pense pas que ce soit une bonne idée car sûr s'il y aurait d'autres implications.

Il est également très difficile de modifier la requête car elle est générée par le framework Magento et nécessiterait une personnalisation de la base de code Magento, ce que j'aimerais éviter. Je ne suis même pas sûr que ce soit la seule requête effectuée.

J'ai inclus les versions mineures pour mes installations MySQL. J'essaye maintenant de mettre à jour MySQL 5.7.17 à 5.7.18 (la dernière version) pour voir s'il y a une mise à jour des performances.

Après la mise à niveau vers MySQL 5.7.18, je n’ai constaté aucune amélioration. Afin de ramener le système à un état stable et hautement performant, nous avons décidé de revenir à MySQL 5.6.30. Après le déclassement, nous avons constaté une amélioration instantanée.

La requête ci-dessus est exécutée dans MySQL 5.6.30 sur le nouveau serveur exécuté en 0.036 secondes.

7
Peter A

Hou la la! C'est la première fois que je vois quelque chose d'utile de Profiling. La création dynamique d'un index est une nouvelle fonctionnalité d'optimisation d'Oracle. Mais il semble que ce n'était pas le meilleur plan pour cette affaire.

Tout d’abord, je vous recommanderai de déposer un bogue à l’adresse http://bugs.mysql.com - ils n’aiment pas les régressions, en particulier cette erreur. Si possible, fournissez EXPLAIN FORMAT=JSON SELECT... et "trace de l'optimiseur". (Je n'accepte pas de peaufiner des réglages obscurs comme une réponse acceptable, mais merci de les avoir découverts.)

Retour à vous aider ...

  • Si vous n'avez pas besoin de LEFT, ne l'utilisez pas. Il retourne NULLs lorsqu'il n'y a pas de lignes correspondantes dans la table 'droite'; cela se produira-t-il dans votre cas?
  • Veuillez fournir SHOW CREATE TABLE. En attendant, je suppose que vous n’avez pas INDEX(include_in_menu, is_active, path). Les deux premiers peuvent être dans l'un ou l'autre ordre; path doit être le dernier.
  • Et INDEX(category_id, is_system, store_id, id_path) avec id_path last.
  • Votre requête semble avoir un motif qui fonctionne bien pour se transformer en une sous-requête:

(Remarque: cela préserve même la sémantique de LEFT.)

SELECT  `main_table`.`entity_id` , main_table.`name` , main_table.`path` ,
        `main_table`.`is_active` , `main_table`.`is_anchor` ,
        ( SELECT  `request_path`
            FROM  url_rewrite
            WHERE  url_rewrite.category_id=main_table.entity_id
              AND  url_rewrite.is_system = 1
              AND  url_rewrite.store_id  = 1
              AND  url_rewrite.id_path LIKE 'category/%' 
        ) as request_path
    FROM  `catalog_category_flat_store_1` AS `main_table`
    WHERE  (main_table.include_in_menu = '1')
      AND  (main_table.is_active = '1')
      AND  (main_table.path like '1/494/%')
      AND  (`level` <= 2)
    ORDER BY  `main_table`.`position` ASC
    LIMIT  0, 1000 

(Les index suggérés s'appliquent ici aussi.)

3
Rick James

THIS n'est pas une réponse uniquement pour un commentaire pour @Nigel Ren

Ici, vous pouvez voir que LIKE utilise également l'index.

mysql> SELECT *
    -> FROM testdb
    -> WHERE
    -> vals LIKE 'text%';
+----+---------------------------------------+
| id | vals                                  |
+----+---------------------------------------+
|  3 | text for line number 3                |
|  1 | textline 1 we rqwe rq wer qwer q wer  |
|  2 | textline 2 asdf asd fas f asf  wer 3  |
+----+---------------------------------------+
3 rows in set (0,00 sec)

mysql> EXPLAIN
    -> SELECT *
    -> FROM testdb
    -> WHERE
    -> vals LIKE 'text%';
+----+-------------+--------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
| id | select_type | table  | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+--------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | testdb | NULL       | range | vals          | vals | 515     | NULL |    3 |   100.00 | Using where; Using index |
+----+-------------+--------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0,01 sec)

mysql>

échantillon avec LEFT ()

mysql> SELECT *
    -> FROM testdb
    -> WHERE
    -> LEFT(vals,4) = 'text';
+----+---------------------------------------+
| id | vals                                  |
+----+---------------------------------------+
|  3 | text for line number 3                |
|  1 | textline 1 we rqwe rq wer qwer q wer  |
|  2 | textline 2 asdf asd fas f asf  wer 3  |
+----+---------------------------------------+
3 rows in set (0,01 sec)

mysql> EXPLAIN
    -> SELECT *
    -> FROM testdb
    -> WHERE
    -> LEFT(vals,4) = 'text';
+----+-------------+--------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
| id | select_type | table  | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+--------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | testdb | NULL       | index | NULL          | vals | 515     | NULL |    5 |   100.00 | Using where; Using index |
+----+-------------+--------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0,01 sec)

mysql>
0
Bernd Buffen