J'exécute une comparaison de performances entre l'utilisation de 1000 instructions INSERT:
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('6f3f7257-a3d8-4a78-b2e1-c9b767cfe1c1', 'First 0', 'Last 0', 0)
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('32023304-2e55-4768-8e52-1ba589b82c8b', 'First 1', 'Last 1', 1)
...
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('f34d95a7-90b1-4558-be10-6ceacd53e4c4', 'First 999', 'Last 999', 999)
..versus en utilisant une seule instruction INSERT avec 1000 valeurs:
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES
('db72b358-e9b5-4101-8d11-7d7ea3a0ae7d', 'First 0', 'Last 0', 0),
('6a4874ab-b6a3-4aa4-8ed4-a167ab21dd3d', 'First 1', 'Last 1', 1),
...
('9d7f2a58-7e57-4ed4-ba54-5e9e335fb56c', 'First 999', 'Last 999', 999)
À ma grande surprise, les résultats sont à l'opposé de ce que je pensais:
- 1000 instructions INSERT: 290 msec.
- 1 instruction INSERT avec 1000 VALEURS: 2800 msec.
Le test est exécuté directement dans MSSQL Management Studio avec SQL Server Profiler utilisé pour la mesure (et j'ai des résultats similaires en l'exécutant à partir de code C # à l'aide de SqlClient, ce qui est encore plus surprenant compte tenu de toutes les allers-retours des couches DAL)
Cela peut-il être raisonnable ou expliqué d'une manière ou d'une autre? Comment se fait-il qu'une méthode supposément plus rapide entraîne des performances 10 fois (!) Moins bonnes?
Je vous remercie.
EDIT: Joindre des plans d'exécution pour les deux:
Réponses:
Votre plan montre que les insertions simples utilisent des procédures paramétrées (éventuellement auto-paramétrées), donc le temps d'analyse / compilation pour ceux-ci doit être minimal.
J'ai pensé que j'examinerais un peu plus cela, alors configurez une boucle ( script ) et essayez d'ajuster le nombre de
VALUES
clauses et d'enregistrer le temps de compilation.J'ai ensuite divisé le temps de compilation par le nombre de lignes pour obtenir le temps de compilation moyen par clause. Les résultats sont ci-dessous
Jusqu'à 250
VALUES
clauses présentes, le temps de compilation / le nombre de clauses a une légère tendance à la hausse mais rien de trop dramatique.Mais ensuite, il y a un changement soudain.
Cette section des données est présentée ci-dessous.
La taille du plan mis en cache, qui avait augmenté de manière linéaire, diminue soudainement, mais CompileTime augmente de 7 fois et CompileMemory augmente. C'est le point de coupure entre un plan paramétré automatiquement (avec 1000 paramètres) et un plan non paramétré. Par la suite, il semble devenir linéairement moins efficace (en termes de nombre de clauses de valeur traitées dans un temps donné).
Je ne sais pas pourquoi cela devrait être. Vraisemblablement, lorsqu'il compile un plan pour des valeurs littérales spécifiques, il doit effectuer une activité qui ne se met pas à l'échelle de manière linéaire (comme le tri).
Cela ne semble pas affecter la taille du plan de requête mis en cache lorsque j'ai essayé une requête composée entièrement de lignes en double et ni l'un ni l'autre n'affecte l'ordre de sortie de la table des constantes (et que vous insérez dans un tas de temps passé à trier serait de toute façon inutile même si c'était le cas).
De plus, si un index clusterisé est ajouté à la table, le plan affiche toujours une étape de tri explicite afin qu'il ne semble pas trier au moment de la compilation pour éviter un tri au moment de l'exécution.
J'ai essayé de regarder cela dans un débogueur mais les symboles publics de ma version de SQL Server 2008 ne semblent pas être disponibles, alors j'ai dû regarder la
UNION ALL
construction équivalente dans SQL Server 2005.Une trace de pile typique est ci-dessous
Donc, en supprimant les noms dans la trace de la pile, il semble passer beaucoup de temps à comparer les chaînes.
Cet article de la base de
DeriveNormalizedGroupProperties
connaissances indique qu'il est associé à ce que l'on appelait auparavant l' étape de normalisation du traitement des requêtesCette étape est maintenant appelée liaison ou algébrizing et elle prend la sortie de l'arbre d'analyse d'expression de l'étape d'analyse précédente et produit un arbre d'expression algébriqué (arbre du processeur de requête) pour aller de l'avant à l'optimisation (optimisation de plan trivial dans ce cas) [ref] .
J'ai essayé une autre expérience ( Script ) qui consistait à réexécuter le test d'origine mais en examinant trois cas différents.
On peut clairement voir que plus les chaînes sont longues, plus les choses se détériorent et qu'à l'inverse, plus il y a de doublons, meilleures sont les choses. Comme mentionné précédemment, les doublons n'affectent pas la taille du plan mis en cache, donc je suppose qu'il doit y avoir un processus d'identification des doublons lors de la construction de l'arbre d'expression algébriqué lui-même.
Éditer
Un endroit où ces informations sont exploitées est montré par @Lieven ici
Parce qu'au moment de la compilation, il peut déterminer que la
Name
colonne n'a pas de doublons, il ignore1/ (ID - ID)
le tri par expression secondaire au moment de l'exécution (le tri dans le plan n'a qu'une seuleORDER BY
colonne) et aucune erreur de division par zéro n'est déclenchée. Si des doublons sont ajoutés à la table, l'opérateur de tri affiche deux ordre par colonnes et l'erreur attendue est générée.la source
<ParameterList>
avec une<ConstantScan><Values><Row>
liste.<ConstantScan><Values><Row>
place de<ParameterList>
.Ce n'est pas trop surprenant: le plan d'exécution du petit insert est calculé une fois, puis réutilisé 1000 fois. L'analyse et la préparation du plan sont rapides, car il n'a que quatre valeurs à supprimer. Un plan de 1000 lignes, par contre, doit traiter 4000 valeurs (ou 4000 paramètres si vous avez paramétré vos tests C #). Cela pourrait facilement réduire le gain de temps que vous gagnez en éliminant 999 allers-retours vers SQL Server, en particulier si votre réseau n'est pas trop lent.
la source
Le problème est probablement lié au temps nécessaire pour compiler la requête.
Si vous souhaitez accélérer les insertions, ce que vous devez vraiment faire est de les envelopper dans une transaction:
À partir de C #, vous pouvez également envisager d'utiliser un paramètre de table. L'émission de plusieurs commandes dans un seul lot, en les séparant par des points-virgules, est une autre approche qui aidera également.
la source
Je suis tombé sur une situation similaire en essayant de convertir une table avec plusieurs lignes de 100 000 avec un programme C ++ (MFC / ODBC).
Comme cette opération a pris très longtemps, j'ai pensé regrouper plusieurs inserts en un seul (jusqu'à 1000 en raison des limitations de MSSQL ). Je suppose que beaucoup d'instructions d'insertion unique créeraient une surcharge similaire à ce qui est décrit ici .
Cependant, il s'avère que la conversion a pris en fait un peu plus de temps:
Ainsi, 1000 appels simples à CDatabase :: ExecuteSql chacun avec une seule instruction INSERT (méthode 1) sont environ deux fois plus rapides qu'un seul appel à CDatabase :: ExecuteSql avec une instruction INSERT multiligne avec 1000 tuples de valeur (méthode 2).
Mise à jour: Donc, la prochaine chose que j'ai essayée était de regrouper 1000 instructions INSERT séparées dans une seule chaîne et de faire exécuter par le serveur (méthode 3). Il s'avère que c'est encore un peu plus rapide que la méthode 1.
Edit: J'utilise Microsoft SQL Server Express Edition (64 bits) v10.0.2531.0
la source