J'essaie d'optimiser une partie de mon code qui insère des données dans MySQL. Dois-je enchaîner les INSERT pour créer un énorme INSERT à plusieurs lignes ou plusieurs INSERT séparés sont-ils plus rapides?
Réponses:
https://dev.mysql.com/doc/refman/8.0/en/insert-optimization.html
Le temps nécessaire pour insérer une ligne est déterminé par les facteurs suivants, où les nombres indiquent des proportions approximatives:
- Connexion: (3)
- Envoi de la requête au serveur: (2)
- Requête d'analyse: (2)
- Insertion d'une ligne: (1 × taille de la ligne)
- Insertion d'index: (1 × nombre d'index)
- Fermeture: (1)
À partir de là, il devrait être évident que l'envoi d'une grande déclaration vous fera économiser une surcharge de 7 par instruction d'insertion, ce qui, en lisant plus loin, le texte dit également:
Si vous insérez plusieurs lignes du même client en même temps, utilisez des instructions INSERT avec plusieurs listes VALUES pour insérer plusieurs lignes à la fois. C'est beaucoup plus rapide (beaucoup plus rapide dans certains cas) que d'utiliser des instructions INSERT séparées sur une seule ligne.
Je sais que je réponds à cette question près de deux ans et demi après qu'elle a été posée, mais je voulais juste fournir des données concrètes d'un projet sur lequel je travaille en ce moment qui montre qu'en effet, faire plusieurs blocs VALUE par insert est BEAUCOUP plus rapide que les instructions INSERT séquentielles de bloc VALUE unique.
Le code que j'ai écrit pour ce benchmark en C # utilise ODBC pour lire les données en mémoire à partir d'une source de données MSSQL (~ 19000 lignes, toutes sont lues avant le début de toute écriture), et le connecteur MySql .NET (Mysql.Data. *) INSÉRER les données de la mémoire dans une table sur un serveur MySQL via des instructions préparées. Il a été écrit de manière à me permettre d'ajuster dynamiquement le nombre de blocs VALUE par INSERT préparé (c'est-à-dire insérer n lignes à la fois, où je pourrais ajuster la valeur de n avant une exécution.) J'ai également exécuté le test plusieurs fois pour chaque n.
L'exécution de blocs VALUE uniques (par exemple, 1 ligne à la fois) a pris 5,7 à 5,9 secondes. Les autres valeurs sont les suivantes:
2 lignes à la fois: 3,5 - 3,5 secondes
5 lignes à la fois: 2,2 - 2,2 secondes
10 lignes à la fois: 1,7 - 1,7 secondes
50 lignes à la fois: 1,17 - 1,18 secondes
100 lignes à la fois: 1,1 - 1,4 secondes
500 lignes à la fois: 1,1 - 1,2 secondes
1000 lignes à la fois: 1,17 - 1,17 secondes
Donc oui, même le simple fait de regrouper 2 ou 3 écritures offre une amélioration spectaculaire de la vitesse (temps d'exécution réduit d'un facteur n), jusqu'à ce que vous arriviez à quelque part entre n = 5 et n = 10, auquel point l'amélioration diminue nettement, et quelque part dans l'intervalle n = 10 à n = 50, l'amélioration devient négligeable.
J'espère que cela aidera les gens à décider (a) d'utiliser ou non l'idée de préparation multiple et (b) du nombre de blocs VALUE à créer par instruction (en supposant que vous souhaitiez travailler avec des données qui peuvent être suffisamment volumineuses pour pousser la requête au-delà de la taille maximale de la requête pour MySQL, qui, je crois, est de 16 Mo par défaut dans de nombreux endroits, peut-être plus ou moins grand en fonction de la valeur de max_allowed_packet définie sur le serveur.)
Un facteur majeur sera de savoir si vous utilisez un moteur transactionnel et si vous avez activé l'autocommit.
La validation automatique est activée par défaut et vous souhaitez probablement la laisser activée; par conséquent, chaque insertion que vous effectuez effectue sa propre transaction. Cela signifie que si vous effectuez une insertion par ligne, vous allez valider une transaction pour chaque ligne.
En supposant un seul thread, cela signifie que le serveur doit synchroniser certaines données sur le disque pour CHAQUE RANG. Il doit attendre que les données atteignent un emplacement de stockage persistant (avec un peu de chance, la mémoire RAM de votre contrôleur RAID). Ceci est intrinsèquement plutôt lent et deviendra probablement le facteur limitant dans ces cas.
Je suppose bien sûr que vous utilisez un moteur transactionnel (généralement innodb) ET que vous n'avez pas modifié les paramètres pour réduire la durabilité.
Je suppose également que vous utilisez un seul thread pour faire ces insertions. L'utilisation de plusieurs threads brouille un peu les choses car certaines versions de MySQL ont une validation de groupe de travail dans innodb - cela signifie que plusieurs threads effectuant leurs propres validations peuvent partager une seule écriture dans le journal des transactions, ce qui est bien car cela signifie moins de synchronisations avec le stockage persistant .
D'un autre côté, le résultat est que vous VOULEZ VRAIMENT UTILISER des insertions à plusieurs rangées.
Il y a une limite au-dessus de laquelle cela devient contre-productif, mais dans la plupart des cas, il s'agit d'au moins 10 000 lignes. Donc, si vous les regroupez jusqu'à 1000 lignes, vous êtes probablement en sécurité.
Si vous utilisez MyISAM, il y a tout un tas d'autres choses, mais je ne vous ennuierai pas avec celles-ci. Paix.
En général, moins il y a d'appels à la base de données, mieux c'est (c'est-à-dire plus rapide, plus efficace), essayez donc de coder les insertions de manière à minimiser les accès à la base de données. N'oubliez pas qu'à moins que vous n'utilisiez un pool de connexions, chaque accès à la base de données doit créer une connexion, exécuter SQL, puis interrompre la connexion. Un peu de frais généraux!
Tu pourrais vouloir :
En fonction de la façon dont votre serveur évolue (c'est définitivement ok avec PostgreSQl
, Oracle
et MSSQL
), faites la chose ci-dessus avec plusieurs threads et plusieurs connexions.
En général, plusieurs inserts seront plus lents en raison de la surcharge de connexion. Faire plusieurs insertions à la fois réduira le coût des frais généraux par insert.
Selon le langage que vous utilisez, vous pouvez éventuellement créer un lot dans votre langage de programmation / script avant d'aller dans la base de données et ajouter chaque insert au lot. Vous pourrez alors exécuter un gros lot en une seule opération de connexion. Voici un exemple en Java.
MYSQL 5.5 Une instruction d'insertion SQL a pris entre 300 et 450 ms. tandis que les statistiques ci-dessous sont pour les déclarations d'insertion multiples en ligne.
(25492 row(s) affected)
Execution Time : 00:00:03:343
Transfer Time : 00:00:00:000
Total Time : 00:00:03:343
Je dirais que l'inline est une bonne solution :)
C'est ridicule à quel point Mysql et MariaDB sont optimisés en ce qui concerne les inserts. J'ai testé mysql 5.7 et mariadb 10.3, pas de réelle différence là-dessus.
J'ai testé cela sur un serveur avec des disques NVME, 70000 IOPS, un débit de 1,1 Go / s seq et c'est possible en duplex intégral (lecture et écriture).
Le serveur est également un serveur haute performance.
Je lui ai donné 20 Go de RAM.
La base de données est complètement vide.
La vitesse que je reçois était de 5000 insertions par seconde lorsque je faisais des insertions à plusieurs lignes (essayé avec des morceaux de 1 Mo jusqu'à 10 Mo de données)
Maintenant, l'indice:
si j'ajoute un autre fil et que j'insère dans les MÊMES tables, j'ai soudainement 2x5000 / sec. Un fil de plus et j'ai 15000 au total / sec
Considérez ceci: lorsque vous effectuez des insertions de threads ONE, cela signifie que vous pouvez écrire séquentiellement sur le disque (avec des exceptions aux index). Lorsque vous utilisez des threads, vous dégradez les performances possibles car il doit maintenant effectuer beaucoup plus d'accès aléatoires. Mais la vérification de la réalité montre que mysql est si mal optimisé que les threads aident beaucoup.
Les performances réelles possibles avec un tel serveur sont probablement des millions par seconde, le processeur est inactif, le disque est inactif.
La raison en est clairement que mariadb tout comme mysql a des retards internes.
id
, parent_id
) VALUES (1, NULL). L'un des ensembles de valeurs suivants est lié à cette ligne. Si vous divisez en morceaux et que cet ensemble arrive à un autre morceau, il peut être traité avant le premier, échouant tout le processus. Une idée de la façon de gérer cela?
plusieurs inserts sont plus rapides, mais il a dû. un autre thrik désactive les vérifications de contraintes temprorary pour faire des insertions beaucoup plus rapidement. Peu importe que votre table l'ait ou non. Par exemple, testez la désactivation des clés étrangères et profitez de la vitesse:
SET FOREIGN_KEY_CHECKS=0;
bien sûr, vous devez le réactiver après les insertions en:
SET FOREIGN_KEY_CHECKS=1;
c'est une manière courante d'insérer d'énormes données. L'intégrité des données peut se rompre, vous devez donc en prendre soin avant de désactiver les vérifications de clé étrangère.