Database
 sql >> Base de données >  >> RDS >> Database

Tapez fortement ces paramètres de table

Les paramètres table existent depuis SQL Server 2008 et fournissent un mécanisme utile pour envoyer plusieurs lignes de données à SQL Server, regroupées en un seul appel paramétré. Toutes les lignes sont alors disponibles dans une variable de table qui peut ensuite être utilisée dans le codage T-SQL standard, ce qui élimine le besoin d'écrire une logique de traitement spécialisée pour décomposer à nouveau les données. De par leur définition même, les paramètres table sont fortement typés dans un type de table défini par l'utilisateur qui doit exister dans la base de données où l'appel est effectué. Cependant, fortement typé n'est pas vraiment "fortement typé" comme on pourrait s'y attendre, comme cet article va le démontrer, et les performances pourraient en être affectées.

Pour démontrer les impacts potentiels sur les performances des paramètres de table mal typés avec SQL Server, nous allons créer un exemple de type de table défini par l'utilisateur avec la structure suivante :

CREATE TYPE dbo.PharmacyData AS TABLE
(
  Dosage        int,
  Drug          varchar(20),
  FirstName     varchar(50),
  LastName      varchar(50),
  AddressLine1  varchar(250),
  PhoneNumber   varchar(50),
  CellNumber    varchar(50),
  EmailAddress  varchar(100),
  FillDate      datetime
);

Ensuite, nous aurons besoin d'une application .NET qui utilisera ce type de table défini par l'utilisateur comme paramètre d'entrée pour transmettre des données à SQL Server. Pour utiliser un paramètre de table à partir de notre application, un objet DataTable est généralement rempli, puis transmis comme valeur du paramètre avec un type de SqlDbType.Structured. Le DataTable peut être créé de plusieurs manières dans le code .NET, mais une manière courante de créer la table ressemble à ceci :

System.Data.DataTable DefaultTable = new System.Data.DataTable("@PharmacyData");
DefaultTable.Columns.Add("Dosage",       typeof(int));
DefaultTable.Columns.Add("Drug",         typeof(string));
DefaultTable.Columns.Add("FirstName",    typeof(string));
DefaultTable.Columns.Add("LastName",     typeof(string));
DefaultTable.Columns.Add("AddressLine1", typeof(string));
DefaultTable.Columns.Add("PhoneNumber",  typeof(string));
DefaultTable.Columns.Add("CellNumber",   typeof(string));
DefaultTable.Columns.Add("EmailAddress", typeof(string));
DefaultTable.Columns.Add("Date",         typeof(DateTime));

Vous pouvez également créer le DataTable en utilisant la définition en ligne comme suit :

System.Data.DataTable DefaultTable = new System.Data.DataTable("@PharmacyData")
{
  Columns =
  {
    {"Dosage",       typeof(int)},
    {"Drug",         typeof(string)},
    {"FirstName",    typeof(string)},
    {"LastName",     typeof(string)},
    {"AddressLine1", typeof(string)},
    {"PhoneNumber",  typeof(string)},
    {"CellNumber",   typeof(string)},
    {"EmailAddress", typeof(string)},
    {"Date",         typeof(DateTime)},
  },
  Locale = CultureInfo.InvariantCulture
};

L'une ou l'autre de ces définitions de l'objet DataTable dans .NET peut être utilisée comme paramètre table pour le type de données défini par l'utilisateur qui a été créé, mais tenez compte de la définition typeof(string) pour les différentes colonnes de chaîne; ceux-ci peuvent tous être "correctement" typés, mais ils ne sont pas réellement fortement typés pour les types de données implémentés dans le type de données défini par l'utilisateur. Nous pouvons remplir la table avec des données aléatoires et la transmettre à SQL Server en tant que paramètre d'une instruction SELECT très simple qui renverra exactement les mêmes lignes que la table que nous avons transmise, comme suit :

using (SqlCommand cmd = new SqlCommand("SELECT * FROM @tvp;", connection))
{
  var pList = new SqlParameter("@tvp", SqlDbType.Structured);
  pList.TypeName = "dbo.PharmacyData";
  pList.Value = DefaultTable;
  cmd.Parameters.Add(pList);
  cmd.ExecuteReader().Dispose();
}

Nous pouvons ensuite utiliser une pause de débogage afin de pouvoir inspecter la définition de DefaultTable lors de l'exécution, comme indiqué ci-dessous :

Nous pouvons voir que MaxLength pour les colonnes de chaîne est défini sur -1, ce qui signifie qu'elles sont transmises via TDS à SQL Server en tant que LOB (Large Objects) ou essentiellement en tant que colonnes typées MAX, ce qui peut avoir un impact négatif sur les performances. Si nous modifions la définition .NET DataTable pour qu'elle soit fortement typée par la définition de schéma du type de table défini par l'utilisateur comme suit et examinons le MaxLength de la même colonne à l'aide d'une pause de débogage :

System.Data.DataTable SchemaTable = new System.Data.DataTable("@PharmacyData")
{
  Columns =
  {
    {new DataColumn() { ColumnName = "Dosage",        DataType = typeof(int)} },
    {new DataColumn() { ColumnName = "Drug",          DataType = typeof(string), MaxLength = 20} },
    {new DataColumn() { ColumnName = "FirstName",     DataType = typeof(string), MaxLength = 50} },
    {new DataColumn() { ColumnName = "LastName",      DataType = typeof(string), MaxLength = 50} },
    {new DataColumn() { ColumnName = "AddressLine1",  DataType = typeof(string), MaxLength = 250} },
    {new DataColumn() { ColumnName = "PhoneNumber",   DataType = typeof(string), MaxLength = 50} },
    {new DataColumn() { ColumnName = "CellNumber",    DataType = typeof(string), MaxLength = 50} },
    {new DataColumn() { ColumnName = "EmailAddress",  DataType = typeof(string), MaxLength = 100} },
    {new DataColumn() { ColumnName = "Date",          DataType = typeof(DateTime)} },
  },
  Locale = CultureInfo.InvariantCulture
};

Nous avons maintenant des longueurs correctes pour les définitions de colonne, et nous ne les transmettrons pas en tant que LOB sur TDS à SQL Server.

Comment cela affecte-t-il les performances, vous vous demandez peut-être? Cela affecte le nombre de tampons TDS envoyés sur le réseau à SQL Server, ainsi que le temps de traitement global des commandes.

L'utilisation exacte du même ensemble de données pour les deux tables de données et l'utilisation de la méthode RetrieveStatistics sur l'objet SqlConnection nous permettent d'obtenir les métriques statistiques ExecutionTime et BuffersSent pour les appels à la même commande SELECT, et d'utiliser simplement les deux définitions DataTable différentes comme paramètres. et l'appel de la méthode ResetStatistics de l'objet SqlConnection permet d'effacer les statistiques d'exécution entre les tests.

La définition GetSchemaTable spécifie correctement le MaxLength pour chacune des colonnes de chaîne où GetTable ajoute simplement des colonnes de type chaîne dont la valeur MaxLength est définie sur -1, ce qui entraîne l'envoi de 100 tampons TDS supplémentaires pour 861 lignes de données dans la table et une durée d'exécution de 158 millisecondes par rapport à seulement 250 tampons envoyés pour la définition DataTable fortement typée et un temps d'exécution de 111 millisecondes. Bien que cela puisse sembler peu dans le grand schéma des choses, il s'agit d'un seul appel, d'une seule exécution, et l'impact accumulé au fil du temps pour plusieurs milliers ou millions d'exécutions de ce type est le moment où les avantages commencent à s'additionner et à avoir un impact notable. sur les performances et le débit de la charge de travail.

Là où cela peut vraiment faire la différence, c'est dans les implémentations cloud où vous payez plus que de simples ressources de calcul et de stockage. Outre les coûts fixes des ressources matérielles pour la machine virtuelle Azure, la base de données SQL ou AWS EC2 ou RDS, il existe un coût supplémentaire pour le trafic réseau vers et depuis le cloud qui est ajouté à la facturation de chaque mois. La réduction des tampons traversant le réseau réduira le coût total de possession de la solution au fil du temps, et les modifications de code nécessaires pour mettre en œuvre ces économies sont relativement simples.