6
votes

Exporter un grand fichier CSV en parallèle à SQL Server

J'ai un grand fichier CSV ... 10 colonnes, 100 millions de lignes, environ 6 Go de taille sur mon disque dur. Je souhaite lire cette ligne de fichier CSV par ligne, puis chargez les données dans une base de données Microsoft SQL Server à l'aide de la copie en vrac SQL. J'ai lu deux coups de discussion ici et aussi sur Internet. La plupart des gens suggèrent que la lecture d'un fichier CSV en parallèle n'achète pas beaucoup en termes d'efficacité que les tâches / threads soumis à l'accès des disques.

Ce que j'essaie de faire est, lisez la ligne par ligne de CSV et ajoutez-la bloquer la collecte de lignes de taille 100K. Et une fois que cette collection est pleine une nouvelle tâche / thread pour écrire les données sur SQL Server à l'aide de l'API SQLBuckCopyCopyCopy.

J'ai écrit cette pièce de code, mais heurte une erreur au moment de l'exécution "Tentative invoquer une copie en vrac sur un objet qui a une opération en attente. " Ce scénario ressemble à quelque chose qui peut être facilement résolu en utilisant .NET 4.0 TPL mais je ne suis pas capable de le faire fonctionner. Toute suggestion sur ce que je fais mal? xxx


5 commentaires

Au lieu de passer du temps à écrire votre propre outil, pourquoi ne pas utiliser un outil ETL qui le fait déjà tel que SQL Server Integration Services.


Avez-vous essayé une version séquentielle de ce code et avez-vous prouvé que la complication de la multi-threading vaut le gain de performance?


Il existe de nombreux guides en ligne pour optimiser les inserts en vrac, c'est-à-dire technique.microsoft.com/en-us/library/ms190421(v=SQL.105).aspx . On dirait que vous essayez de résoudre un problème que vous n'avez pas prouvé existe. Je vous suggère d'abord d'obtenir une base de référence simplement en utilisant bcp.exe , puis essayez de vous améliorer à ce moment-là.


De ce que j'ai lu en ligne ... SQLBulkCopy est beaucoup plus rapide que l'outil d'importation de données intégré que SQL Server a pour lequel je pense utiliser SSIS sous les couvertures. La performance de la charge est essentielle et d'où mon enquête sur la rédaction de ma propre application LIL


J'avais des volumes similaires et dans mon cas, le disque IO de mon serveur SQL était le goulot d'étranglement, alors j'ai divisé les lots, mais je ne suis pas allé parallèle.


3 Réponses :


5
votes

http://joshclose.github.io/csvhelper/

https://efbulkinssert.codeplex.com/ p>

Si possible pour vous, je vous suggère de lire votre fichier dans une liste à l'aide de la CSVHelper susmentionnée et écrivez sur votre dB à l'aide d'un insert en vrac pendant que vous faites ou efbulkinsert que j'ai utilisé et que je suis incroyablement rapide. p>

using CsvHelper;

public static List<T> CSVImport<T,TClassMap>(string csvData, bool hasHeaderRow, char delimiter, out string errorMsg) where TClassMap : CsvHelper.Configuration.CsvClassMap
    {
        errorMsg = string.Empty;
        var result = Enumerable.Empty<T>();

        MemoryStream memStream = new MemoryStream(Encoding.UTF8.GetBytes(csvData));
        StreamReader streamReader = new StreamReader(memStream);
        var csvReader = new CsvReader(streamReader);

        csvReader.Configuration.RegisterClassMap<TClassMap>();
        csvReader.Configuration.DetectColumnCountChanges = true;
        csvReader.Configuration.IsHeaderCaseSensitive = false;
        csvReader.Configuration.TrimHeaders = true;
        csvReader.Configuration.Delimiter = delimiter.ToString();
        csvReader.Configuration.SkipEmptyRecords = true;
        List<T> items = new List<T>();

        try
        {
            items = csvReader.GetRecords<T>().ToList();
        }
        catch (Exception ex)
        {
            while (ex != null)
            {
                errorMsg += ex.Message + Environment.NewLine;

                foreach (var val in ex.Data.Values)
                    errorMsg += val.ToString() + Environment.NewLine;

                ex = ex.InnerException;
            }
        }
        return items;
    }
}


3 commentaires

Le CSV semble être très grand. (6 Go). Est-ce que le getrecords (). Tolist () Tout charger à la mémoire?


Oui - Bon point, ce n'est peut-être pas possible pour lui. Bulkinserting dans une golfe est une grosse épisolée. Peut-être qu'il peut appeler prendre () sur la liste pour le décrocher un peu. Sa liste semble s'intégrer à une chaîne créée par fichier.Readlines.


Je ne peux pas charger tout le fichier CSV en mémoire en raison de la taille. J'ai donc besoin de lire 100k lignes à la fois, puis de l'écrire sur SQL Server à l'aide de Bulkinsert. Et oui, je veux écrire toute la table à une fois pas une ligne à la fois.



3
votes

Vous pouvez créer une procédure de magasin et transmettre l'emplacement de fichier comme ci-dessous

CREATE PROCEDURE [dbo].[CSVReaderTransaction]
    @Filepath varchar(100)=''
AS
-- STEP 1: Start the transaction
BEGIN TRANSACTION

-- STEP 2 & 3: checking @@ERROR after each statement
EXEC ('BULK INSERT Employee FROM ''' +@Filepath
        +''' WITH (FIELDTERMINATOR = '','', ROWTERMINATOR = ''\n'' )')

-- Rollback the transaction if there were any errors
IF @@ERROR <> 0
 BEGIN
    -- Rollback the transaction
    ROLLBACK

    -- Raise an error and return
    RAISERROR ('Error in inserting data into employee Table.', 16, 1)
    RETURN
 END

COMMIT TRANSACTION


1 commentaires

ne fonctionne pas pour moi coz Le fichier est sur ma machine locale et que le serveur SQL est sur une machine différente et que la machine n'a pas d'accès à distance à mon lecteur local.



6
votes

Ne pas.

L'accès parallèle peut ne pas vous donner une lecture plus rapide du fichier (ce ne sera pas, mais je ne vais pas me battre que bataille ...) Mais pour certains écrit parallèles, il a gagné 't vous donner un insert en vrac plus rapide. En effet, l'insert de vrac minimalement enregistré (c.-à-d. Le vraiment rapide insert en vrac) nécessite une serrure de table. Voir Conditions préalables pour la journalisation minimale d'importation en vrac :

La journalisation minimale nécessite que la table cible réponde aux conditions suivantes:

...
- Le verrouillage de la table est spécifié (viablier) .
...

Les insertions parallèles, par définition, ne peuvent pas obtenir de serrures de table simultanées. QED. Vous aboiez un mauvais arbre.

Arrêtez d'obtenir vos sources de la recherche aléatoire sur Internet. Lire Le Guide de performance de chargement des données , est le guide pour ... Chargement des données performantes.

Je vous recommanderais d'arrêter d'inventer la roue. Utilisez un SSIS , c'est exactement quoi est conçu pour gérer.


3 commentaires

D'accord. Pouvez-vous me signaler à un package SSIS existant que la masse inserdit les données d'un fichier CSV dans une table SQL? Je ne veux pas réinventer le volant comme vous l'avez dit et que vous souhaitez donc utiliser une solution préexistante au lieu de créer un paquet SSIS seul.


Tout ce dont vous avez besoin est un Source de fichier plat connecté à un Destination OLEDB avec un ensemble de charge rapide. Voir Importer un fichier CSV dans la table de base de données à l'aide de SSIS , par exemple.


>> Mais pour certaines écrites parallèles, cela ne vous donnera pas plus de masse en vrac. << Ce n'est pas vrai. J'ai utilisé quelque chose de similaire, de points de course PowerShell, avec verrouillage de table, et je suis passé de 90 000 rangées / sec sur 140 000 rangées / s.