2010-07-29 10 views
5

Donc, je travaille sur une base de données que je vais ajouter à mes futurs projets comme une sorte de db de support, mais je vais avoir un peu de problème avec lui, en particulier les journaux.Sql Server 2008 Réglage avec des transactions importantes (700k + lignes/transaction)

La base de données doit être mise à jour une fois par mois. La table principale doit être purgée puis rechargée à partir d'un fichier CSV. Le problème est que Sql Server va générer un journal pour ce qui est grand MEGA. J'ai réussi à le remplir une fois, mais je voulais tester tout le processus en le purgeant et en le remplissant ensuite.

C'est quand j'obtiens une erreur que le fichier journal est rempli. Il passe de 88 Mo (après réduction par le biais du plan de maintenance) à 248 Mo, puis interrompt complètement le processus et ne le termine jamais. J'ai plafonné sa croissance à 256 Mo, en incrémentant de 16 Mo, ce qui explique pourquoi il a échoué, mais en réalité je n'en ai pas besoin pour tout enregistrer. Existe-t-il un moyen de contourner complètement la journalisation sur toute requête exécutée sur la base de données?

Merci pour les réponses à l'avance!

EDIT: Selon les suggestions de @ mattmc3, j'ai implémenté SqlBulkCopy pour toute la procédure. Cela fonctionne AMAZING, sauf que ma boucle est en train de planter sur le tout dernier morceau qui doit être inséré. Je ne sais pas trop où je me trompe, je ne sais même pas si c'est une bonne boucle, alors j'apprécierais de l'aide.

Je sais que c'est un problème avec les derniers appels GetDataTable ou SetSqlBulkCopy. Je suis en train d'insérer des lignes 788189, 788000 entrer et les 189 restants déferlent ...

string[] Rows; 

using (StreamReader Reader = new StreamReader("C:/?.csv")) { 
    Rows = Reader.ReadToEnd().TrimEnd().Split(new char[1] { 
     '\n' 
    }, StringSplitOptions.RemoveEmptyEntries); 
}; 

int RowsInserted = 0; 

using (SqlConnection Connection = new SqlConnection("")) { 
    Connection.Open(); 

    DataTable Table = null; 

    while ((RowsInserted < Rows.Length) && ((Rows.Length - RowsInserted) >= 1000)) { 
     Table = GetDataTable(Rows.Skip(RowsInserted).Take(1000).ToArray()); 

     SetSqlBulkCopy(Table, Connection); 

     RowsInserted += 1000; 
    }; 

    Table = GetDataTable(Rows.Skip(RowsInserted).ToArray()); 

    SetSqlBulkCopy(Table, Connection); 

    Connection.Close(); 
}; 

static DataTable GetDataTable(
    string[] Rows) { 
    using (DataTable Table = new DataTable()) { 
     Table.Columns.Add(new DataColumn("A")); 
     Table.Columns.Add(new DataColumn("B")); 
     Table.Columns.Add(new DataColumn("C")); 
     Table.Columns.Add(new DataColumn("D")); 

     for (short a = 0, b = (short)Rows.Length; a < b; a++) { 
      string[] Columns = Rows[a].Split(new char[1] { 
       ',' 
      }, StringSplitOptions.RemoveEmptyEntries); 

      DataRow Row = Table.NewRow(); 

      Row["A"] = Columns[0]; 
      Row["B"] = Columns[1]; 
      Row["C"] = Columns[2]; 
      Row["D"] = Columns[3]; 

      Table.Rows.Add(Row); 
     }; 

     return (Table); 
    }; 
} 

static void SetSqlBulkCopy(
    DataTable Table, 
    SqlConnection Connection) { 
    using (SqlBulkCopy SqlBulkCopy = new SqlBulkCopy(Connection)) { 
     SqlBulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping("A", "A")); 
     SqlBulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping("B", "B")); 
     SqlBulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping("C", "C")); 
     SqlBulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping("D", "D")); 

     SqlBulkCopy.BatchSize = Table.Rows.Count; 
     SqlBulkCopy.DestinationTableName = "E"; 
     SqlBulkCopy.WriteToServer(Table); 
    }; 
} 

EDIT/CODE FINAL: Ainsi, l'application est maintenant terminée et fonctionne incroyable, et très rapide! @ mattmc3, merci pour toute l'aide! Voici le code final pour quiconque peut le trouver utile:

List<string> Rows = new List<string>(); 

using (StreamReader Reader = new StreamReader(@"?.csv")) { 
    string Line = string.Empty; 

    while (!String.IsNullOrWhiteSpace(Line = Reader.ReadLine())) { 
     Rows.Add(Line); 
    }; 
}; 

if (Rows.Count > 0) { 
    int RowsInserted = 0; 

    DataTable Table = new DataTable(); 

    Table.Columns.Add(new DataColumn("Id")); 
    Table.Columns.Add(new DataColumn("A")); 

    while ((RowsInserted < Rows.Count) && ((Rows.Count - RowsInserted) >= 1000)) { 
     Table = GetDataTable(Rows.Skip(RowsInserted).Take(1000).ToList(), Table); 

     PerformSqlBulkCopy(Table); 

     RowsInserted += 1000; 

     Table.Clear(); 
    }; 

    Table = GetDataTable(Rows.Skip(RowsInserted).ToList(), Table); 

    PerformSqlBulkCopy(Table); 
}; 

static DataTable GetDataTable(
    List<string> Rows, 
    DataTable Table) { 
    for (short a = 0, b = (short)Rows.Count; a < b; a++) { 
     string[] Columns = Rows[a].Split(new char[1] { 
      ',' 
     }, StringSplitOptions.RemoveEmptyEntries); 

     DataRow Row = Table.NewRow(); 

     Row["A"] = ""; 

     Table.Rows.Add(Row); 
    }; 

    return (Table); 
} 

static void PerformSqlBulkCopy(
    DataTable Table) { 
    using (SqlBulkCopy SqlBulkCopy = new SqlBulkCopy(@"", SqlBulkCopyOptions.TableLock)) { 
     SqlBulkCopy.BatchSize = Table.Rows.Count; 
     SqlBulkCopy.DestinationTableName = ""; 
     SqlBulkCopy.WriteToServer(Table); 
    }; 
} 
+0

Quelques suggestions si vous voulez accélérer encore plus. 1.) Au lieu de faire Reader.ReadToEnd(), faites une boucle et faites Reader.ReadLine() une ligne à la fois. Ça prendra moins de mémoire. 2.) Si personne n'accède à votre table pendant le chargement, utilisez l'option 'SqlBulkCopyOptions.TableLock'. 3.Pour vous sauver du code, l'objet SqlBulkCopy infère vos mappages de colonnes si vous nommez les colonnes de la même manière que dans votre table de destination et puisque vous gérez vous-même le découpage, il n'y a aucune raison de définir le .BatchSize non plus. Codage heureux! – mattmc3

+0

Au sujet de la déduction des colonnes, cela fonctionnera-t-il si: 'DBTable = {Id (PK, IDENTITÉ), A, B, C, D}', mais 'DataTable = {A, B, C, D}'? Je pense que ça me donnait des ennuis, c'est pour ça que je les ai spécifiés, mais là encore, j'aurais pu me tromper ... – Gup3rSuR4c

+0

Eh bien, c'est fait! J'ai mis en œuvre tout ce que vous avez recommandé et cela fonctionne incroyable! La mémoire a été réduite de moitié à ~ 85 Mo et l'ensemble de l'opération prend environ 45 secondes à compléter. Et j'ai compris la chose des colonnes ci-dessus, j'avais raison, mais j'ai juste ajouté un espace réservé pour le 'Id 'et cela a fonctionné. 'MERCI DE M'AIDER SUR CELA ET DE M'INSCRIVIR SUR DES CHOSES QUE JE NE SAIS JAMAIS !!!' – Gup3rSuR4c

Répondre

5

Si vous faites une insertion en bloc dans la table dans SQL Server, comment vous devriez faire cela (BCP, Bulk Insert, Insert Into...Select ou .NET, la classe SqlBulkCopy) vous pouvez utiliser le « vrac journalisé » modèle de récupération. Je recommande fortement de lire les articles MSDN sur les modèles de récupération: http://msdn.microsoft.com/en-us/library/ms189275.aspx

+0

Je pensais que le SqlBulkCopy (qui est ce que j'utiliserais depuis que l'opération est effectuée par une application de console) est censé être utilisé de Table en Table? – Gup3rSuR4c

+0

Oh non ... c'est de DataTable à une table SQL Server. Vous chargez les données dans un objet System.Data.DataTable qui correspond à votre table de destination. Vous pouvez obtenir ces données dans le DataTable à partir d'un fichier, d'une requête, de vos objets busniess ... comme vous le souhaitez. Je recommande d'obtenir un morceau de 1000 ou plus d'enregistrements, de faire la copie en bloc via l'objet SqlBulkCopy, puis d'effacer le DataTable et de faire un autre morceau. Dans les coulisses, l'objet SqlBulkCopy utilise simplement les mêmes fonctionnalités qu'une instruction «Bulk Insert». J'ai fait ETL de cette façon pendant des années et c'est rapide et simple. – mattmc3

+0

Ok. J'essaye d'écrire le code maintenant, je n'ai jamais utilisé de DataTables auparavant et je dois me souvenir des bonnes méthodes d'ol Sql parce que je ne les ai pas utilisées depuis que Linq est sorti ... :) En tout cas, merci pour l'aide ! – Gup3rSuR4c

1

Il n'existe aucun moyen de contourner l'utilisation du journal des transactions dans SQL Server.

+2

Vous devez utiliser le journal - c'est vrai. Mais vous pouvez minimiser l'impact de la journalisation en sélectionnant des modèles de récupération alternatifs et en étant stratégique sur la manière dont vous insérez vos données. – mattmc3

2

Vous pouvez définir le modèle Recover pour chaque base de données séparément. Peut-être le simple modèle de récupération travaillera pour vous. Le modèle simple:

Récupère automatiquement l'espace du journal pour limiter les besoins en espace, éliminant ainsi la nécessité de gérer l'espace du journal des transactions.

Lisez à ce sujet here.