Question

J'ai eu quelque chose comme ça dans mon code (.Net 2.0, MS SQL)

SqlConnection connection = new SqlConnection(@"Data Source=localhost;Initial
Catalog=DataBase;Integrated Security=True");
  connection.Open();

  SqlCommand cmdInsert = connection.CreateCommand();
  SqlTransaction sqlTran = connection.BeginTransaction();
  cmdInsert.Transaction = sqlTran;

  cmdInsert.CommandText =
     @"INSERT INTO MyDestinationTable" +
      "(Year, Month, Day, Hour,  ...) " +
      "VALUES " +
      "(@Year, @Month, @Day, @Hour, ...) ";

  cmdInsert.Parameters.Add("@Year", SqlDbType.SmallInt);
  cmdInsert.Parameters.Add("@Month", SqlDbType.TinyInt);
  cmdInsert.Parameters.Add("@Day", SqlDbType.TinyInt);
  // more fields here
  cmdInsert.Prepare();

  Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);

  StreamReader reader = new StreamReader(stream);
  char[] delimeter = new char[] {' '};
  String[] records;
  while (!reader.EndOfStream)
  {
    records = reader.ReadLine().Split(delimeter, StringSplitOptions.None);

    cmdInsert.Parameters["@Year"].Value = Int32.Parse(records[0].Substring(0, 4));
    cmdInsert.Parameters["@Month"].Value = Int32.Parse(records[0].Substring(5, 2));
    cmdInsert.Parameters["@Day"].Value = Int32.Parse(records[0].Substring(8, 2));
    // more here complicated stuff here
    cmdInsert.ExecuteNonQuery()
  }
  sqlTran.Commit();
  connection.Close();

Avec cmdInsert.ExecuteNonQuery () commenté, ce code s'exécute en moins de 2 secondes. Avec l'exécution SQL, cela prend 1m 20 sec. Il y a environ 0,5 million de disques. La table est vide avant. La tâche de flux de données SSIS ayant une fonctionnalité similaire prend environ 20 secondes.

  • L'insertion en bloc n'était pas une option (voir ci-dessous). J'ai fait des trucs sophistiqués lors de cette importation.
  • Ma machine de test est Core 2 Duo avec 2 Go de RAM.
  • Lors de la recherche dans le gestionnaire de tâches, l’UC n’était pas entièrement finalisée. IO ne semblait pas non plus être pleinement utilisé.
  • Le schéma est simple comme bonjour: une table avec AutoInt comme index primaire et moins de 10 pouces, minuscules et caractères (10).

Après quelques réponses, j'ai découvert qu'il était possible d'exécuter la copie en bloc de la mémoire ! Je refusais d'utiliser la copie en bloc parce que je pensais que cela devait être fait à partir d'un fichier ...

Maintenant, je l'utilise et cela prend environ 20 secondes (comme une tâche SSIS)

  DataTable dataTable = new DataTable();

  dataTable.Columns.Add(new DataColumn("ixMyIndex", System.Type.GetType("System.Int32")));   
  dataTable.Columns.Add(new DataColumn("Year", System.Type.GetType("System.Int32")));   
  dataTable.Columns.Add(new DataColumn("Month", System.Type.GetType("System.Int32")));
  dataTable.Columns.Add(new DataColumn("Day", System.Type.GetType("System.Int32")));
 // ... and more to go

  DataRow dataRow;
  object[] objectRow = new object[dataTable.Columns.Count];

  Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);

  StreamReader reader = new StreamReader(stream);
  char[] delimeter = new char[] { ' ' };
  String[] records;
  int recordCount = 0;
  while (!reader.EndOfStream)
  {
    records = reader.ReadLine().Split(delimeter, StringSplitOptions.None);

    dataRow = dataTable.NewRow();
    objectRow[0] = null; 
    objectRow[1] = Int32.Parse(records[0].Substring(0, 4));
    objectRow[2] = Int32.Parse(records[0].Substring(5, 2));
    objectRow[3] = Int32.Parse(records[0].Substring(8, 2));
    // my fancy stuf goes here

    dataRow.ItemArray = objectRow;         
    dataTable.Rows.Add(dataRow);

    recordCount++;
  }

  SqlBulkCopy bulkTask = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, null);
  bulkTask.DestinationTableName = "MyDestinationTable"; 
  bulkTask.BatchSize = dataTable.Rows.Count;
  bulkTask.WriteToServer(dataTable);
  bulkTask.Close();
Était-ce utile?

La solution

Au lieu d'insérer chaque enregistrement individuellement, essayez d'utiliser la SqlBulkCopy pour insérer en bloc tous les enregistrements à la fois.

Créez un DataTable et ajoutez tous vos enregistrements au DataTable, puis utilisez SqlBulkCopy . WriteToServer pour une insertion en masse toutes les données à la fois.

Autres conseils

La transaction est-elle requise? L'utilisation de transaction nécessite beaucoup plus de ressources que de simples commandes.

Aussi Si vous êtes sûr que les valeurs insérées sont correctes, vous pouvez utiliser un BulkInsert.

1 minute semble assez raisonnable pour 0,5 million d'enregistrements. C'est un record toutes les 0,00012 secondes.

La table a-t-elle des index? Le fait de les supprimer et de les réappliquer après l'insertion en bloc améliorerait les performances des inserts, si cela était une option.

Il ne me semble pas déraisonnable de traiter 8 333 enregistrements par seconde ... quel type de débit attendez-vous?

Si vous avez besoin d'une meilleure vitesse, vous pouvez envisager de mettre en place une insertion en masse:

http://msdn.microsoft.com/en-us/library /ms188365.aspx

Si une forme quelconque d’insertion en bloc n’est pas une option, l’autre solution consisterait en plusieurs threads, chacun avec sa propre connexion à la base de données.

Le problème avec le système actuel est que vous avez 500 000 allers-retours dans la base de données et que vous attendez que le premier aller-retour soit terminé avant de commencer le suivant - toute sorte de latence (c'est-à-dire un réseau entre les machines) signifiera que vous passiez le plus clair de votre temps à attendre.

Si vous pouvez scinder le travail, en utilisant peut-être une configuration de producteur / consommateur, vous constaterez peut-être que vous pouvez utiliser beaucoup plus toutes les ressources.

Cependant, pour ce faire, vous devrez perdre la grande transaction. Sinon, le premier thread d'écriture bloquera tous les autres jusqu'à la fin de la transaction. Vous pouvez toujours utiliser des transactions, mais vous devrez utiliser beaucoup de petites transactions plutôt qu'une seule grande.

SSIS sera rapide car il utilise la méthode de l'insertion en bloc: effectuez tout d'abord le traitement compliqué, générez la liste finale des données à insérer et transmettez le tout en même temps à l'insertion en bloc.

Je suppose que l'insertion physique de 500 000 enregistrements prend environ 58 secondes. Vous obtenez donc environ 10 000 insertions par seconde. Sans connaître les spécifications de votre serveur de base de données (je vois que vous utilisez localhost, les retards de réseau ne devraient donc pas être un problème), il est difficile de dire si cela est bon, mauvais ou catastrophique.

Je regarderais votre schéma de base de données - y a-t-il un tas d'index sur la table qui doivent être mis à jour après chaque insertion? Cela pourrait provenir d'autres tables avec des clés étrangères faisant référence à la table sur laquelle vous travaillez. Il existe des outils de profilage SQL et des fonctionnalités de surveillance des performances intégrés à SQL Server, mais je ne les ai jamais utilisés. Mais ils peuvent présenter des problèmes tels que des serrures et des choses comme ça.

Faites les choses les plus fantaisistes sur les données, sur tous les enregistrements en premier. Puis Bulk-Insert.

(puisque vous ne faites pas de sélections après une insertion. Je ne vois pas le problème de l'application de toutes les opérations sur les données avant le BulkInsert

Si je devais deviner, la première chose que je rechercherais serait un nombre trop élevé ou un type d’index erroné dans la table tbTrafficLogTTL. Sans regarder la définition du schéma de la table, je ne peux pas vraiment le dire, mais j'ai rencontré des problèmes de performances similaires lorsque:

  1. La clé primaire est un GUID et l'index principal est CLUSTERED.
  2. Il existe une sorte d'index UNIQUE sur un ensemble de champs.
  3. Il y a trop d'index dans la table.

Lorsque vous commencez à indexer un demi-million de lignes de données, le temps nécessaire à la création et à la maintenance des index s'additionne.

Je noterai également que si vous avez la possibilité de convertir les champs Year, Month, Day, Hour, Minute, Second en un seul champ datetime2 ou timestamp, vous devriez le faire. Vous ajoutez beaucoup de complexité à votre architecture de données, sans aucun gain. La seule raison pour laquelle je voudrais même envisager d'utiliser une structure à champs divisés comme celle-ci est si vous traitez avec un schéma de base de données préexistant qui ne peut pas être modifié pour une raison quelconque. Dans ce cas, ça craint d’être toi.

J'ai eu un problème similaire lors de mon dernier contrat. Vous faites 500 000 allers-retours vers SQL pour insérer vos données. Pour une augmentation spectaculaire des performances, vous souhaitez étudier la méthode BulkInsert dans l'espace de noms SQL. J'avais " recharger " processus qui allaient de 2 heures ou plus pour restaurer quelques dizaines de tables à 31 secondes une fois que j’ai implémenté l’importation en bloc.

Cela pourrait être mieux accompli en utilisant quelque chose comme la commande bcp. Si ce n’est pas disponible, les suggestions ci-dessus concernant l’utilisation de BULK INSERT sont votre meilleur choix. Vous effectuez 500 000 allers-retours dans la base de données et écrivez 500 000 entrées dans les fichiers journaux, sans parler de l'espace qui doit être alloué au fichier journal, à la table et aux index.

Si vous insérez dans un ordre différent de celui de votre index clusterisé, vous devez également gérer le temps nécessaire à la réorganisation des données physiques sur le disque. De nombreuses variables peuvent rendre votre requête plus lente que vous ne le voudriez.

~ 10 000 transactions par seconde, ce n’est pas terrible pour les insertions individuelles qui vont et viennent à partir du code /

BULK INSERT = bcp à partir d'une autorisation

Vous pouvez regrouper les INSERT pour réduire les allers-retours SQLDataAdaptor.UpdateBatchSize = 10000 donne 50 allers-retours

Vous avez toujours 500 000 inserts ...

Article

MSDN

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top