Lequel est le plus rapide: plusieurs INSERTs simples ou un INSERT à plusieurs lignes?

189

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?

dusoft
la source

Réponses:

293

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.

mbarkhau
la source
29
Comment cette réponse s'applique-t-elle si plusieurs INSERT uniques se trouvent dans la même transaction de base de données?
Pincer
2
Combien de lignes je peux insérer à la fois en utilisant une instruction d'insertion unique. est-ce que je peux insérer 10000 lignes à la fois?
Naresh Ramoliya
11
@Pinch L'utilisation d'une transaction tout en effectuant ~ 1,5k d'upserts (insertion / mises à jour) a réduit la durée de l'opération de ~ 1,5 seconde à ~ 0,2 seconde. Ou en d'autres termes, il l'a rendu 86% plus rapide par rapport aux inserts à une rangée. Zut.
fgblomqvist
1
Remarque: semble être très différent dans MSSQL: stackoverflow.com/questions/8635818
...
Que diriez-vous d'utiliser Prepared Statement pour insérer plusieurs insertions simples répétitives?
priyabagus le
152

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.)

Jon Kloske
la source
1
Demande de clarification: est votre temps "secondes par ligne" ou "secondes au total".
EngrStudent
3
Nombre total de secondes - les secondes par ligne sont donc divisées par les ~ 19 000 lignes. Bien que ce soit un petit nombre, les lignes / seconde sont peut-être une meilleure métrique si vous recherchez un nombre facilement comparable.
Jon Kloske
Incidemment, il y a un exemple de code .NET pour l'approche que je décris ci-dessus sur cette réponse connexe: stackoverflow.com/questions/25377357/...
Jon Kloske
18

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.

MarkR
la source
1
Y a-t-il une raison pour laquelle cela devient contre-productif après un point? J'ai déjà vu cela arriver mais je ne savais pas pourquoi.
Dhruv Gairola
1
Savez-vous s'il y a un point quelconque dans le traitement par lots des insertions MySQL lors de l' utilisation de transactions . Je me demande simplement si je peux me sauver la peine d'avoir à générer la commande SQL à valeurs multiples si ma bibliothèque sous-jacente (Java JDBC - mysql-connector-java-5.1.30) ne s'engage pas jusqu'à ce que je le lui dise.
RTF
@RTF Je pense que vous devrez effectuer un petit test pour déterminer ce comportement dans votre situation car il s'agit d'un comportement hautement spécifique à la mise en œuvre, mais dans de nombreux cas, les transactions oui devraient fournir des gains de performances similaires.
Jasmine Hegman
9

Envoyez autant d'inserts que possible sur le fil à la fois. La vitesse d'insertion réelle doit être la même, mais vous constaterez des gains de performances grâce à la réduction de la surcharge du réseau.

RC.
la source
7

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!

ennuikiller
la source
et si la connexion persistante est utilisée?
dusoft
6
Il y a encore des frais généraux. Le temps de transit seul (aller et retour pour chaque insert séparé) sera rapidement perceptible si vous faites des milliers d'inserts.
RC.
4

Tu pourrais vouloir :

  • Vérifiez que l'auto-commit est désactivé
  • Connexion ouverte
  • Envoyez plusieurs lots d'inserts en une seule transaction (taille d'environ 4000-10000 lignes? Voyez-vous)
  • Fermer la connexion

En fonction de la façon dont votre serveur évolue (c'est définitivement ok avec PostgreSQl, Oracleet MSSQL), faites la chose ci-dessus avec plusieurs threads et plusieurs connexions.

Antibarbie
la source
3

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.

Chris Williams
la source
3

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 :)

A_01
la source
0

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.

John
la source
@Craftables vous avez besoin d'un développement externe, cela ne peut pas être fait dans mysql. Threads signifie que vous utilisez plusieurs connexions au serveur, vous divisez la requête en plusieurs morceaux (par exemple en la divisant en parties paires par clé primaire). J'ai réussi à obtenir jusqu'à 10 000 fois les performances en utilisant cette méthode sur de très grandes tables. Les requêtes qui s'exécuteraient pendant 40 000 secondes peuvent se terminer en 2-3 minutes SI vous utilisez plusieurs threads et que votre mysql est hautement optimisé.
John
@John Intéressant et peut avoir de très belles applications ... mais ... Si vous divisez la requête en plusieurs morceaux, comment gérez-vous les transactions? Et considérez également le scénario suivant: La table x a une colonne «parent_id» qui se rapporte à la même table «id». Quelque part dans vos données, vous avez INSERT INTO x ( 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?
zozo
@zozo ceci est utile pour les insertions groupées et les requêtes groupées. Les transactions ruineraient de toute façon les performances car elles incluent beaucoup de tampons de données. Mais vous pouvez également utiliser des transactions dans des insertions ou des requêtes multithreads.
John
-3

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.

MSS
la source
1
Je ne sais pas pourquoi ppl a voté pour cela pour deux raisons: 1. Cela n'a rien à voir avec la question 2. C'est une très mauvaise idée (à quelques exceptions près - comme le dumping ou les changements structurels de la température -, mais mauvaise en général). Les contrôles sont là pour une raison: ils sont là pour assurer la cohérence des données. Ils ralentissent les choses parce qu'ils garantissent que vous n'insérez pas ou ne modifiez pas des données que vous ne devriez pas. Essayez d'optimiser les requêtes de la bonne manière; dans tout environnement critique pour l'entreprise, cela signifierait la mort de l'application car, quelle que soit votre attention, les choses échoueront à un moment donné.
zozo
1
peut-être, mais cette option est extrêmement efficace pour importer de grandes tables et très pratique et elle pourrait donner à certaines personnes une idée de la façon dont elles peuvent rendre l'insertion de données beaucoup plus rapide.
MSS