parser très gros fichier texte

Répondre


Cette question est un moyen d’empêcher des soumissions automatisées de formulaires par des robots.
Smileys
:D :) :( :o :shock: :? 8-) :lol: :x :P :oops: :cry: :evil: :twisted: :roll: :wink: :!: :?: :idea: :arrow: :| :mrgreen: =D> #-o =P~ :^o :non: :priere: 8-|
Voir plus de smileys
  Revue du sujet
 

  Étendre la vue Revue du sujet : parser très gros fichier texte

par Cyrano » 06 juil. 2006, 09:31

Bon, ben j'ai résolu : le type de champ bloquant est théoriquement un FLOAT, mais l'importation de la donnée absente posait problème malgré une valeur par défaut à NULL. comme c'est une base de transit, j'ai modifié le type de champ par un VARCHAR... et là tout est rentré dans l'ordre.

Sujet donc résolu, merci à chacun.

par Cyrano » 05 juil. 2006, 17:39

Merci pour cette réponse titerm, mais ça fait bien longtemps que c'est fait d'une part, et d'autre part, le problème se pose au moment de récupérer la donnée (absente) dans le fichier pour l'envoyer vers la table, et non au moment de l'insertion.

Comme j'envoie vers une base de transit, je me suis pas embarrassé de détails pour les types de champs et les valeurs par défaut (pratiquement tout à NULL). Après il sera toujours temps de fignoler pour envoyer vers la vraie base définitive avec tous les détails voulus, mais avec entre autres un fichier de + de 670Mo, je peux pas finasser.

par titerm » 05 juil. 2006, 17:35

Lors de la création de table, pour chaque colonne, essai de mettre une valeur par default à NULL

par Cyrano » 05 juil. 2006, 15:38

Le problème est résolu et la vitesse m'a carrément scotché, voyez plutôt :

Code : Tout sélectionner

mysql> LOAD DATA LOCAL INFILE 'G:/www/tmp/******/***********/addition.txt' -> IGNORE -> INTO TABLE `Addition` -> FIELDS TERMINATED BY '\t' -> LINES TERMINATED BY '\n' -> (`ADDMarket`, `ADDVehType`, `ADDID`, `ADDNatCode`, `ADDEQCode`, -> `ADDVal`, `ADDValUntil`, `ADDCurrency`, `ADDPrice1`, `ADDPrice2`, -> `ADDTaxRt`, `ADDTax1`, `ADDTax2`, `ADDNet`, `ADDFlag`, -> `ADDFlagPack`, `ADDStatus`, `ADDRecStatus`, `ADDRecDate`, `ADDTargetGrp`, -> `ADDAssemble`, `ADDMerchantCode`, `ADDValNewPr`); Query OK, 7583821 rows affected, 65535 warnings (1 min 16.53 sec) Records: 7583821 Deleted: 0 Skipped: 0 Warnings: 7583821
7,5 millions de lignes en moins de deux minutes, je crois que la question est quasiment réglée.

Il me reste toutefois un problème avec certains fichiers dont certains champs n'ont pas de donnée du tout, ce qui fait que j'ai deux tabulations cosécutives : le retour est une erreur comme ceci:

Code : Tout sélectionner

ERROR 1265 (01000): Data truncated for column 'EXCTax1' at row 1
Je résouds le problème en ne mettant pas le champ concerné dans la liste parce que toute le colonne est pareille, mais existe-t-il une solution au cas où j'aurais la donnée sur certaines lignes seulement ? Attention, je n'ai absolument aucun contrôle possible sur l'export du fichier utilisé. En clair, comment indiquer qu'il faut insérer "NULL" dans la colonne s'il n'y a rien entre les deux tabulations ?

par Hubert Roksor » 05 juil. 2006, 13:43

cache d'opcode pour TOUS les traitements un peu trop lourd pour la memoire ou le processeur :!:
Attention à ne pas confondre, les caches d'opcode n'accélèrent que le démarrage du programme mais une fois le fichier chargé en mémoire ils ne servent plus à rien. Ils ne modifient pas son utilisation de la mémoire ou du processeur. Pour être complet, on pourrait dire que la plupart des caches d'opcodes possèdent également un optimiseur, mais leur action est généralement extrêmement limitée (moins de 1% d'amélioration).
mise en cache du code, tu diviseras au minimum par 2 le temps [...]
Ça je n'ai pas compris, de quel cache parles-tu ?

par jeff » 05 juil. 2006, 11:28

au passage
etant que cyrano pense utilisé MySQL 5, aurait-il interet a utilisé une procedure stockée???

par Lorenzo » 05 juil. 2006, 11:09

......., mais je cherche une manière d'accélérer un brin le processus : est-ce que l'un d'entre vous aurait une idée à me suggérer.
cache d'opcode pour TOUS les traitements un peu trop lourd pour la memoire ou le processeur :!:
mise en cache du code, tu diviseras au minimum par 2 le temps mais il restera le probleme du temps total de traitement car pour traiter des fichiers CSV de 100Mo je mets dans les 8mn :?

-zend cache (payant !)
-APC (gratuit) http://pecl.php.net/package/APC
-.... il y en a beaucoup d'autres ...

pour PHP, tu peux aussi te creer tes propres fonctions dans des DLL que tu charges par dl() mais mon niveau n'est pas assez bon en C ou delphi pour te donner + d'infos ...

il existe la meme soluce pour MySQL

par Cyrano » 05 juil. 2006, 07:28

Merci pour l'info, je vais tester ça dans la matinée et je ferai des bench pour vérifier :)

par Hubert Roksor » 04 juil. 2006, 23:57

Attention, l'option REPLACE pourrait être incompatible avec la désactivation des indexes.

Essaie de comparer LOAD DATA ... REPLACE et un TRUNCATE suivi de LOAD DATA (avec indexes désactivés) il est souvent plus rentable de faire de nombreux INSERT dans une table vide que des REPLACE sur une table pleine.

par Cyrano » 04 juil. 2006, 22:37

Pour résumer, on achète une base complète dont on va utiliser une bonne partie pour créer un produit qui sera ensuite vendu. J'aurai chaque mois une version à jour de la base complete. Mes fichiers seront des fichiers textes tels que je les ai décris en donnant un exemple dans mon message de départ : le LOAD DATA INFILE avec l'option REPLACE devrait être très simple. Pour ce qui est des index, j'ai pas mal de tables avec des clés composites, mais effectivement, l'idée de desactiver les index devrait notablement accélerer le processus. Faudra peut-être que je fasse quelques bench pour décider de la manière. Je vous raconterai, notre propre base n'est pas encore complètement finalisée de toutes façons.

par Hubert Roksor » 04 juil. 2006, 22:31

Si tu n'as pas besoin de transactions, LOAD DATA sur MyISAM est très très très rapide. Je ne saurais pas dire pour InnoDB, mais je doute que ce soit lent.

N'oublie pas de jeter un œil du côté des conseils pour améliorer les performances d'INSERT. Dans ton cas, le seul conseil qui s'applique est celui de désactiver puis réactiver les index, sauf si tu as une clé primaire sur la table. Dans ce cas, laisse les index et augmente le plus possible le cache d'index par key_buffer_size. (c'est parce que MySQL est obligé de vérifier les contraintes d'unicité donc tu ne peux pas désactiver les indexes, qui sont d'ailleurs très sollicités)

Tiens-nous au courant ;)

par Cyrano » 04 juil. 2006, 19:43

Bon, ben c'est bien ce que je craignais et ça va exclure totalement ma première solution : j'ai eu des infos et le plus gros fichier pèse aux environs de 670Mo. Selon le fournisseur, il faut environ 30mn pour le charger dans une base Oracle, j'ai hâte de voir ça dans une base MySQL... :-k

Quoiqu'il en soit, je vais faire des tests demain avec un LOAD DATA INFILE en mettant un timeout à une heure : on verra bien, je vous raconterai peut-être mes aventures si j'ai des soucis ;)

par Cyrano » 04 juil. 2006, 14:08

Merci bien pour toutes ces réponses, je viens tout juste de revenir pour jeter un coup d'oeil et la solution de LOAD DATA INFILE était dans les options auxquelles j'ai pensé aussi. Le seul problème est que je devrai dans ce cas avoir une base intermédiaire parce que je n'ai pas besoin obligatoirement de toutes les données, la base intermédiaire étant la copie de celle d'où sortent ces données et sur laquelle je n'ai absolument aucun acces.

D'autant qu'en plus, si j'ai évoqué des fichiers de 32Mo, c'est le plus gros que j'ai actuellement dans un jeu d'essai : je m'attends à pire dans les fichier que va nous envoyer le fournisseur à qui on achète ces données.

@ Albat : couper les fichiers serait à la rigueur envisageable, mais je vais devoir commencer par les récupérer par des fonctions ftp. Si la taille est trop importante et que même avec un load data infile ça plante tout, ce sera peut-être une partie de la solution.
Merci tout me monde, je mets pas ça en résolu pour l'instant, je testerai avant et on verra.

++

Re: parser très gros fichier texte

par Hubert Roksor » 04 juil. 2006, 13:46

Je confirme : LOAD DATA est super rapide donc si ton fichier est correctement formaté c'est la solution vers laquelle t'orienter si tu souhaites simplement importe son contenu sans appliquer de transformations à la volée. Enfin, même ça c'est possible dans les versions récentes si mes souvenirs sont bons, mais là tout de suite je le déconseillerais un peu.

Sinon, le bout de script que tu as posté possède le gros défaut de ne pas être "scalable". La quantité de mémoire et le temps CPU utiliséssont directement proportionnels à la taille des fichiers à cause de file() et de la mise en tableau du résultat. Pour les gros fichiers, il vaut mieux utiliser quelque chose comme fread() et ne stocker qu'une petites partie des enregistrements en mémoire tampon. Si le but recherché est l'import des données alors j'essaierais plutôt quelque chose comme
// Tampon d'enregistrements à sauvegarder
$insert = array();
$i = 0;

$filepath = 'lefichier.txt';
$fp = fopen($filepath, 'r');

while ($row = fscanf($fp, "%s\t%s\t%s\n"))
{
	$insert[] = "('" . $db->escape_string($row[0]) . "','" . $db->escape_string($row[1]) . "','" . $db->escape_string($row[2]) . "')";

	if (++$i == 100)
	{
		// On insère les enregistrements 100 par 100
		$sql = 'INSERT DELAYED INTO table (foo, bar, baz) VALUES ' . implode(',', $insert);
		$db->query($sql);

		$insert = array();
		$i = 0;
	}
}

if ($i)
{
	// On insère les enregistrements restants
	$sql = 'INSERT DELAYED INTO table (foo, bar, baz) VALUES ' . implode(',', $insert);
	$db->query($sql);

	unset($insert);
}
Attention, si l'import plante au milieu du te retrouveras avec une demi-table, donc il te faudra peut-être mettre le tout dans une transaction :) (auquel cas, le "DELAYED" devient inutile)

par naholyr » 04 juil. 2006, 13:35

C'est pourquoi faire tes données ? Si tu n'es pas obligé de les traiter en bloc un usage de fopen()/fgets() sera plus performant que file() au niveau occupation mémoire et vitesse.