web-dev-qa-db-fra.com

Meilleures pratiques pour l'importation de fichiers CSV volumineux

Mon entreprise reçoit chaque mois un ensemble de fichiers CSV contenant des informations bancaires que je dois importer dans une base de données. Certains de ces fichiers peuvent être assez volumineux. Par exemple, l'un représente environ 33 Mo et environ 65 000 lignes.

En ce moment, j'ai une application symfony/Doctrine (PHP) qui lit ces fichiers CSV et les importe dans une base de données. Ma base de données a environ 35 tables différentes et sur le processus d'importation, je prends ces lignes, les divise en leurs objets constitutifs et les insère dans la base de données. Tout fonctionne à merveille, sauf qu'il est lent (chaque ligne prend environ un quart de seconde) et qu'il utilise beaucoup de mémoire.

L'utilisation de la mémoire est si mauvaise que je dois diviser mes fichiers CSV. Un fichier de 20 000 lignes arrive à peine. Au moment où il est presque terminé, j'en suis à 95% de l'utilisation de la mémoire. L'importation de ce fichier de 65 000 lignes n'est tout simplement pas possible.

J'ai trouvé que symfony était un cadre exceptionnel pour la construction d'applications et je ne penserais normalement pas à utiliser autre chose, mais dans ce cas, je suis prêt à jeter toutes mes idées préconçues au nom de la performance. Je ne suis engagé dans aucune langue spécifique, SGBD ou quoi que ce soit.

Stack Overflow n'aime pas les questions subjectives, je vais donc essayer de rendre cela aussi non subjectif que possible: pour ceux d'entre vous qui ont non seulement une opinion mais expérience dans l'importation de gros fichiers CSV, quels outils/pratiques avez-vous tilisé dans le passé qui ont réussi?

Par exemple, utilisez-vous simplement l'ORM/OOP de Django et vous n'avez rencontré aucun problème? Ou lisez-vous l'intégralité du fichier CSV en mémoire et préparez-vous quelques énormes instructions INSERT?

Encore une fois, je ne veux pas seulement une opinion, mais quelque chose qui a réellement fonctionné pour vous dans le passé.

Edit: je ne suis pas en train d'importer une feuille de calcul CSV à 85 colonnes dans une table de base de données à 85 colonnes. Je normalise les données et les place dans des dizaines de tableaux différents. Pour cette raison, je ne peux pas simplement utiliser LOAD DATA INFILE (J'utilise MySQL) ou toute autre fonctionnalité de SGBD qui lit simplement les fichiers CSV.

De plus, je ne peux utiliser aucune solution spécifique à Microsoft.

24
Jason Swett

J'ai eu exactement le même problème il y a environ 2 semaines. J'ai écrit quelques .NET pour faire des insertions ROW BY ROW et d'après mes calculs avec la quantité de données que j'avais, il faudrait environ une semaine pour le faire de cette façon.

Au lieu de cela, j'ai utilisé un générateur de chaînes pour créer une énorme requête et l'ai envoyée à mon système relationnel en une seule fois. Cela est passé d'une semaine à 5 minutes. Maintenant, je ne sais pas quel système relationnel vous utilisez, mais avec d'énormes requêtes, vous devrez probablement modifier votre paramètre max_allowed_packet ou similaire.

11
kmarks2

Pardonnez-moi si je ne comprends pas exactement votre problème correctement, mais il semble que vous essayez simplement d'obtenir une grande quantité de données CSV dans une base de données SQL. Y a-t-il une raison pour laquelle vous souhaitez utiliser une application Web ou un autre code pour traiter les données CSV en instructions INSERT? J'ai réussi à importer de grandes quantités de données CSV dans SQL Server Express (version gratuite) à l'aide de SQL Server Management Studio et à l'aide d'instructions BULK INSERT. Un simple insert en vrac ressemblerait à ceci:

BULK INSERT [Company].[Transactions]
    FROM "C:\Bank Files\TransactionLog.csv"
    WITH
    (
        FIELDTERMINATOR = '|',
        ROWTERMINATOR = '\n',
        MAXERRORS = 0,
        DATAFILETYPE = 'widechar',
        KEEPIDENTITY
    )
GO
17
Lucifer Sam

Premièrement: 33 Mo est pas grand. MySQL peut facilement gérer des données de cette taille.

Comme vous l'avez remarqué, l'insertion ligne par ligne est lente. L'utilisation d'un ORM en plus est encore plus lente: il y a des frais généraux pour la construction d'objets, la sérialisation, etc. L'utilisation d'un ORM pour ce faire sur 35 tables est encore plus lente . Ne fais pas ça.

Vous pouvez en effet utiliser LOAD DATA INFILE; il suffit d'écrire un script qui transforme vos données au format souhaité, en les séparant en fichiers par table dans le processus. Vous pouvez ensuite LOAD chaque fichier dans la table appropriée. Ce script peut être écrit dans n'importe quelle langue.

En dehors de cela, la fonction INSERT (column, ...) VALUES ... en vrac fonctionne également. Ne devinez pas quelle devrait être la taille de votre lot de lignes; chronométrez-le empiriquement , car la taille de lot optimale dépendra de la configuration de votre base de données particulière (configuration du serveur, types de colonnes, indices, etc.)

INSERT ne sera pas aussi rapide que LOAD DATA INFILE, Et vous devrez toujours écrire un script pour transformer les données brutes en requêtes INSERT utilisables. Pour cette raison, je ferais probablement LOAD DATA INFILE Si possible.

5
candu

FWIW les étapes suivantes ont provoqué une énorme accélération de mon LOAD DATA INFILE:

SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET SESSION tx_isolation='READ-UNCOMMITTED';
SET sql_log_bin = 0;
#LOAD DATA LOCAL INFILE....
SET UNIQUE_CHECKS = 1;
SET FOREIGN_KEY_CHECKS = 1;
SET SESSION tx_isolation='READ-REPEATABLE';

Voir l'article ici

4
Sam

Je n'aime pas certaines des autres réponses :)

Je faisais ça au boulot.

Vous écrivez un programme pour créer un gros script SQL plein d'instructions INSERT, une par ligne. Ensuite, vous exécutez le script. Vous pouvez enregistrer le script pour référence future (journal bon marché). Utilisez gzip et il réduira la taille comme 90%.

Vous n'avez pas besoin d'outils sophistiqués et peu importe la base de données que vous utilisez.

Vous pouvez faire quelques centaines d'insertions par transaction ou toutes en une seule transaction, c'est à vous de décider.

Python est un bon langage pour cela, mais je suis sûr que php est bien aussi.

Si vous rencontrez des problèmes de performances, certaines bases de données comme Oracle ont un programme de chargement en masse spécial qui est plus rapide que les instructions INSERT.

Vous devez manquer de mémoire car vous ne devez analyser qu'une seule ligne à la fois. Vous n'avez pas besoin de garder le tout en mémoire, ne faites pas ça!

2
Glen P

Vous pouvez utiliser Mysql LOAD DATA INFILE Statemnt, il vous permet de lire les données d'un fichier texte et d'importer les données du fichier dans une table de base de données très rapidement.

LOAD DATA INFILE '/opt/lampp/htdocs/sample.csv' INTO TABLE discounts FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 ROWS (title,@expired_date,discount) SET expired_date = STR_TO_DATE(@expired_date, '%m/%d/%Y');

pour plus d'informations: http://dev.mysql.com/doc/refman/5.5/en/load-data.html et http://www.mysqltutorial.org/import -csv-file-mysql-table /

2
R T

Vous pouvez utiliser le générateur pour un fichier efficace en mémoire prêt. Le petit extrait ci-dessous pourrait vous aider.

#Method
public function getFileRecords($params)
{
    $fp = fopen('../' . $params['file'] . '.csv', 'r');
    //$header = fgetcsv($fp, 1000, ','); // skip header

    while (($line = fgetcsv($fp, 1000, ',')) != FALSE) {
        $line = array_map(function($str) {
            return str_replace('\N', '', $str);
        }, $line);

        yield $line;
    }

    fclose($fp);

    return;
}

#Implementation
foreach ($yourModel->getFileRecords($params) as $row) {
    // you get row as an assoc array;
    $yourModel->save($row);
}
1

Si vous utilisez Sql Server et avez accès à .NET, vous pouvez écrire une application rapide pour utiliser la classe SQLBulkCopy . Je l'ai utilisé dans des projets précédents pour obtenir très rapidement beaucoup de données dans SQL. La classe SQLBulkCopy utilise le BCP de SQL Server, donc si vous utilisez autre chose que .NET, il peut être utile de vérifier si cette option vous est également ouverte. Je ne sais pas si vous utilisez une base de données autre que SQL Server.

1
Paul Hadfield

Je lis un fichier CSV qui compte près de 1 million d'enregistrements et 65 colonnes. Chaque 1000 enregistrements traités en PHP, il y a une grosse instruction MySQL qui va dans la base de données. L'écriture ne prend aucun temps. C'est l'analyse qui fait. La mémoire utilisée pour traiter ce fichier 600 Mo non compressé est d'environ 12 Mo.

0
Cyril Joudieh

Je dois aussi le faire de temps en temps (importer de grands CSV non standardisés où chaque ligne crée une douzaine d'objets DB liés), j'ai donc écrit un script python où je peux spécifier ce qui va où et comment tout cela est lié. Le script génère alors simplement des instructions INSERT.

Le voici: csv2db

Avertissement: je suis fondamentalement un noob en ce qui concerne les bases de données, il pourrait donc y avoir de meilleures façons d'y parvenir.

0
Lukas