Si les TVP sont "sensiblement plus lents" que les autres options, il est fort probable que vous ne les implémentiez pas correctement.
- Vous ne devez pas utiliser un DataTable, à moins que votre application ne l'utilise en dehors de l'envoi des valeurs au TVP. Utilisation de
IEnumerable<SqlDataRecord>
L'interface est plus rapide et utilise moins de mémoire car vous ne dupliquez pas la collection en mémoire uniquement pour l'envoyer à la base de données. J'ai ceci documenté dans les endroits suivants :- Comment puis-je insérer 10 millions d'enregistrements dans les plus brefs délais ? (beaucoup d'informations supplémentaires et de liens ici aussi)
- Passer le dictionnaire à la procédure stockée T-SQL
- Diffusion de données Dans SQL Server 2008 à partir d'une application (sur SQLServerCentral.com ; inscription gratuite requise)
-
Vous ne devez pas utiliser
AddWithValue
pour le SqlParameter, bien qu'il ne s'agisse probablement pas d'un problème de performances. Mais quand même, ça devrait être :SqlParameter tvp = com.Parameters.Add("data", SqlDbType.Structured); tvp.Value = MethodThatReturnsIEnumerable<SqlDataRecord>(MyCollection);
- Les TVP sont des variables de table et, en tant que telles, ne conservent pas de statistiques. Cela signifie qu'ils signalent n'avoir qu'une seule ligne à l'optimiseur de requête. Donc, dans votre proc, soit :
- Utilisez la recompilation au niveau de l'instruction sur toutes les requêtes utilisant le TVP pour autre chose qu'un simple SELECT :
OPTION (RECOMPILE)
- Créer une table temporaire locale (c'est-à-dire un seul
#
) et copiez le contenu du TVP dans la table temporaire - Vous pouvez essayer d'ajouter une clé primaire en cluster au type de table défini par l'utilisateur
- Si vous utilisez SQL Server 2014 ou une version plus récente, vous pouvez essayer d'utiliser les tables OLTP en mémoire/optimisées en mémoire. Veuillez consulter :Table temporaire et variable de table plus rapides grâce à l'optimisation de la mémoire
- Utilisez la recompilation au niveau de l'instruction sur toutes les requêtes utilisant le TVP pour autre chose qu'un simple SELECT :
Concernant pourquoi vous voyez :
insert into @data ( ... fields ... ) values ( ... values ... )
-- for each row
insert into @data ( ... fields ... ) values ( ... values ... )
au lieu de :
insert into @data ( ... fields ... )
values ( ... values ... ),
( ... values ... ),
SI c'est bien ce qui se passe, alors :
- Si les insertions sont effectuées dans le cadre d'une transaction, il n'y a pas de réelle différence de performances
- La nouvelle syntaxe de la liste de valeurs (c'est-à-dire
VALUES (row1), (row2), (row3)
) est limité à quelque chose comme 1000 lignes et n'est donc pas une option viable pour les TVP qui n'ont pas cette limite. CEPENDANT, ce n'est probablement pas la raison pour laquelle des insertions individuelles sont utilisées, étant donné qu'il n'y a pas de limite lors de l'utilisation deINSERT INTO @data (fields) SELECT tab.[col] FROM (VALUES (), (), ...) tab([col])
, que j'ai documenté ici :Nombre maximal de lignes pour le constructeur de valeurs de table . Au lieu de cela... - La raison est probablement que les insertions individuelles permettent de diffuser les valeurs du code de l'application vers SQL Server :
- à l'aide d'un itérateur (c'est-à-dire le
IEnumerable<SqlDataRecord>
noté au n° 1 ci-dessus), le code de l'application envoie chaque ligne telle qu'elle est renvoyée par la méthode, et - construire les
VALUES (), (), ...
list, même en faisant leINSERT INTO ... SELECT FROM (VALUES ...)
approche (qui n'est pas limitée à 1000 lignes), qui nécessiterait toujours de construire l'entierVALUES
liste avant d'envoyer tout des données dans SQL Server. S'il y a beaucoup de données, cela prendrait plus de temps pour construire la chaîne super longue, et cela prendrait beaucoup plus de mémoire en le faisant.
- à l'aide d'un itérateur (c'est-à-dire le
Veuillez également consulter ce livre blanc de l'équipe de conseil client SQL Server :Maximiser le débit avec TVP