ottscho
Goto Top

While Schleife - Zu bestimmter Stelle springen

Moin,

ich habe ein Import, mit welcher ich 100t Datensätze aus einer CSV in eine DB importiere.
Das Ganze läuft über ein Script, welches sich wegen Servertimeout immer wieder selbst aufruft.
Es gibt ein Step Wert und ein Offset Wert.

Step=500
Offset0

D.h. Gestartet wird mit dem Datensatz 0 und es werden 500 abgearbeitet. Dann wird das Script erneut aufgerufen mit dem Step=500 und dem Offset= 500.
Jetzt werden die ersten 500 Datensätze übersprungen und dann geht es ab 500 weiter mit dem Import. usw.


$row = 1; // Anzahl der Arrays
			$handle = fopen ("upload/artikel.csv","r"); // Datei zum Lesen �ffnen  
			// Die erste Zeile mit den Spaltennamen auslesen
			$data = fgetcsv ($handle, 100000, ";");  
			if(is_array($data)) {
			   foreach($data AS $cellNr => $cellName) {
				   $cellNamesArray[$cellNr] = $cellName;
			   }
			}
			$return=false;		
			while ( ($data = fgetcsv ($handle, 100000, ";")) !== FALSE ) { // Daten werden aus der Datei  
				$num = count ($data); // Felder im Array $data
				//var_dump('row: '.$row); 
				//var_dump('offset: '.$offset); 
				//var_dump('step: '.$step); 
				
				if ($row <= $offset) {
					$row++; // Anzahl der Arrays wird
					continue;
				}
				if ($row > $offset+$step) {
					$return=true;
					break;
				}
				$row++; // Anzahl der Arrays wird
				$count = 0;
				for ($c=0; $c < $num; $c++) { // FOR-Schleife, um Felder des Arrays auszugeben
					$feld[$cellNamesArray[$c]] = $data[$c];
					$count++;	
				}
				
				//**********************//
				//*** IMPORT Routine ***//
				//**********************//

                         }
                                



Jetzt meine Frage. Je länge, das Script läuft, um so langsamer wird es bei den letzte Datensätzen. Da ja z.B bei dem Offset 50000, davor in der Schleife erst mal 49999 übersprungen werden, bis dann ab 50000 die nächsten 500 importiert werden.

Es muss doch möglich sein, direkt den Einstiegspunkt mitzugeben. So dass nicht erst die bereits importierten Datensätze übersprungen werden müssen.

ich hoffe ihr versteht wie ich das meine ;)

Besten Dank

Gruß ottscho

Content-ID: 203001

Url: https://administrator.de/contentid/203001

Ausgedruckt am: 25.11.2024 um 14:11 Uhr

110135
110135 08.03.2013 um 08:50:55 Uhr
Goto Top
Zitat von @ottscho:
Moin

Hallo


ich habe ein Import, mit welcher ich 100t Datensätze aus einer CSV in eine DB importiere.
Das Ganze läuft über ein Script, welches sich wegen Servertimeout immer wieder selbst aufruft.
Es gibt ein Step Wert und ein Offset Wert.

Step=500
Offset0

D.h. Gestartet wird mit dem Datensatz 0 und es werden 500 abgearbeitet. Dann wird das Script erneut aufgerufen mit dem Step=500
und dem Offset= 500.
Jetzt werden die ersten 500 Datensätze übersprungen und dann geht es ab 500 weiter mit dem Import. usw.

Idee ist okay.. aber:


Jetzt meine Frage. Je länge, das Script läuft, um so langsamer wird es bei den letzte Datensätzen. Da ja z.B bei
dem Offset 50000, davor in der Schleife erst mal 49999 übersprungen werden, bis dann ab 50000 die nächsten 500
importiert werden.


Und hier hast du den Grund dafür. Das Teil muss ja so jedes Mal alle vorherigen Datensätze "überspringen".

 				if ($row <= $offset) {
 					$row++; // Anzahl der Arrays wird
 					continue;
 				}
 				if ($row > $offset+$step) {
 					$return=true;
 					break;
 				}
 				$row++; // Anzahl der Arrays wird

Du hättest jetzt folgende Möglichkeiten, die Ausführungszeit hoch zu setzen - was ich nicht besonders schön finde.
Leider weiß ich nicht, wie du genau den Import löst.
Läuft das per MySQL und INSERT's?

Wenn ja, dann schau dir mal folgende Funktion an:
http://dev.mysql.com/doc/refman/5.1/de/load-data.html
ottscho
ottscho 08.03.2013 um 09:12:59 Uhr
Goto Top
danke für dein input.

Es läuft über eine API der Shopschnittstelle.
Hinter der API stehen dann MYSQL INSERTS und UPDATES.

Die API selbst kann ich nicht anpassen.
110135
110135 08.03.2013 um 09:16:18 Uhr
Goto Top
Ah okay,

vielleicht wäre noch folgendes möglich:

Du durchläufst die 5000 INSERTs und nimmst dann die ersten 5000 Einträge aus der Datei raus.
Somit wird die Datei kleiner und du kannst dir die Durchläufe der Schleife sparen.
nxclass
nxclass 08.03.2013 um 09:17:48 Uhr
Goto Top
Das Ganze läuft über ein Script, welches sich wegen Servertimeout immer wieder selbst aufruft
... umgehen kann man dies in dem man das php Skript direkt auf der Konsole aufruft. Oder man macht den Shell Aufruf aus deinem Web-PHP Skript
$return = `nohup php mein_scipt.php >stdout.log 2>stderr.log </dev/null & echo $!`;
so bekommst du die Prozess ID zurück und hast die Ausgaben in Log Dateien.
64748
64748 08.03.2013 aktualisiert um 10:05:06 Uhr
Goto Top
Guten Morgen,
Zitat von @ottscho:
...
Jetzt meine Frage. Je länge, das Script läuft, um so langsamer wird es bei den letzte Datensätzen. Da ja z.B bei
dem Offset 50000, davor in der Schleife erst mal 49999 übersprungen werden, bis dann ab 50000 die nächsten 500
importiert werden.

Es muss doch möglich sein, direkt den Einstiegspunkt mitzugeben. So dass nicht erst die bereits importierten Datensätze
ich hab von PHP keine Ahnung, soviel vorweg face-wink

ich würde, wenn ich sowas mit Perl mache, einen etwas anderen Ansatz wählen (oder zumindest ausprobieren).
Eine Schleife 50000 mal zu durchlaufen kostet Zeit. Stattdessen würde ich die letzte verarbeitet Zeile (oder eine Teilzeichenkette daraus) in einer Datei speichern. Wenn das Programm wieder startet, dann öffnet es die csv-Datei und liest die Zeichenkette ein. Wenn nun in der geöffneten Datei nach der Zeichenkette gesucht wird (PHP wird ja eine Variante von grep haben), dann geht das höchstwahrscheinlich erheblich schneller als Schleifendurchlauf. Die Geschwindigkeit hängt auch davon ab, welchen Teilstring man verwendet (sollte so verschieden als möglich von anderen Strings sein).


Gerade ausprobiert mit Perl, es bringt nichts.

Markus
Lochkartenstanzer
Lochkartenstanzer 08.03.2013 um 09:57:10 Uhr
Goto Top
Moin,

was ich mich frage ist, warum man eine Interpretersprache dafür nimmt? Ich würde da einfach ein C-Binary daraus machen (notfall smit PHC & gcc) und dann das aufrufen.

Was spricht eigentlich dagegen die Eingangsdatei einfach in Häppchen zu je 500 Datensätzen (als Einzeldateien) aufzuteilen und dann den Import auf jeweils diese Häppchen loszulassen?

lks
ottscho
ottscho 08.03.2013 um 10:05:44 Uhr
Goto Top
moin,


was ich mich frage ist, warum man eine Interpretersprache dafür nimmt? Ich würde da einfach ein C-Binary daraus >machen (notfall smit PHC & gcc) und dann das aufrufen.

Naja, die Shopschnittstelle für den Import ist eine API in PHP.

Was spricht eigentlich dagegen die Eingangsdatei einfach in Häppchen zu je 500 Datensätzen (als Einzeldateien) >aufzuteilen und dann den Import auf jeweils diese Häppchen loszulassen?

nichts. so läuft im moment der erste ansatz. wir machen 10t csv Splittdateien. Sprich die Eingangsdatei mit den 120t Artikel wird in 12 Einzeldateien gesplittet.

Dies sollte schon sehr viel Zeit sparen.
Lochkartenstanzer
Lochkartenstanzer 08.03.2013 um 10:47:21 Uhr
Goto Top
Zitat von @ottscho:
moin,


>was ich mich frage ist, warum man eine Interpretersprache dafür nimmt? Ich würde da einfach ein C-Binary daraus
>machen (notfall smit PHC & gcc) und dann das aufrufen.

Naja, die Shopschnittstelle für den Import ist eine API in PHP.

Da gibt es trotzdem Möglichkeiten, wie gesagt zur Not mal PCH ausprobieren.


>Was spricht eigentlich dagegen die Eingangsdatei einfach in Häppchen zu je 500 Datensätzen (als Einzeldateien)
>aufzuteilen und dann den Import auf jeweils diese Häppchen loszulassen?

nichts. so läuft im moment der erste ansatz. wir machen 10t csv Splittdateien. Sprich die Eingangsdatei mit den 120t Artikel
wird in 12 Einzeldateien gesplittet.

Dies sollte schon sehr viel Zeit sparen.

Sind die CSV-Datensätze "einzeilig", d.h. pro Datensatz eine zeile oder sind da zeilenumbrüche drin? Wenn sie einzeilig sind, könnte man durch ein einfaches

cat csv-datei | head -n start+500 | tail -n 500 >temp.csv

eine temporäre Datei erzeugen, die genau die gerade zu bearbeitenden Sätze enthält und schnell genug sein sollte

lks
64748
64748 08.03.2013 aktualisiert um 10:57:58 Uhr
Goto Top
Zitat von @Lochkartenstanzer:
...
> cat csv-datei | head -n start+500 | tail -n 500 >temp.csv
> 

eine temporäre Datei erzeugen, die genau die gerade zu bearbeitenden Sätze enthält und schnell genug sein sollte
das sieht gut aus, ist jedenfalls dreimal so schnell wie mein Versuch mit Perl face-wink
den Teil mit cat bis zum ersten '|' kann man auch weglassen und direkt die Ausgabe von head nach tail pipen.

Spricht etwas dagegen, keine temporäre Datei anzulegen, sondern die 500 Zeilen direkt an das PHP-Programm zu übergeben? Dazu bräuchte man ein kleines Shellskript welches dann auch das PHP-Programm aufruft.

Markus
ottscho
ottscho 08.03.2013 um 10:57:47 Uhr
Goto Top
cat csv-datei | head -n start+500 | tail -n 500 >temp.csv

Sind Einzeilig. Das sieht gut aus. So werden wir es probieren.
ottscho
ottscho 08.03.2013 um 10:59:01 Uhr
Goto Top
Zitat von @64748:
> Zitat von @Lochkartenstanzer:
> ...
>
> > cat csv-datei | head -n start+500 | tail -n 500 >temp.csv
> > 
>
> eine temporäre Datei erzeugen, die genau die gerade zu bearbeitenden Sätze enthält und schnell genug sein
sollte
das sieht gut aus, ist jedenfalls dreimal so schnell wie mein Versuch mit Perl face-wink
den Teil mit cat bis zum ersten '|' kann man auch weglassen und direkt die Ausgabe von head nach tail pipen.

Spricht etwas dagegen, keine temporäre Datei anzulegen, sondern die 500 Zeilen direkt an das PHP-Programm zu übergeben?
Dazu bräuchte man ein kleines Shellskript welches dann auch das PHP-Programm aufruft.

Markus

Nein, spricht nichts dagen.
64748
64748 08.03.2013 aktualisiert um 12:05:05 Uhr
Goto Top
Zitat von @ottscho:
...
Nein, spricht nichts dagen.
ich wollte eigentlich genauer sagen: man reduziert damit die Anzahl der Dateiöffnungen um die Hälfte. Außerdem werden diese nur noch von head gemacht was viel schneller sein dürfte als mit PHP.

Interessantes Thema übrigens, mich würde interessieren, wenn es fertig ist, wie es mit der tatsächlichen Zeitersparnis gegenüber Eurem ersten Ansatz aussieht.

Markus
Lochkartenstanzer
Lochkartenstanzer 08.03.2013 um 11:05:28 Uhr
Goto Top
Zitat von @64748:
> Zitat von @Lochkartenstanzer:
> ...
>
> > cat csv-datei | head -n start+500 | tail -n 500 >temp.csv
> > 
den Teil mit cat bis zum ersten '|' kann man auch weglassen und direkt die Ausgabe von head nach tail pipen.

Normal hätte ich das als
cat csv-datei | head -n start+500 | tail -n 500 | tee temp.cs
geschrieben. face-smile

Ich benutze gern cat, weil ich solche Ketten meist "interaktiv" aufbaue und so einfach ein weiteres "|" mit Befehlen zwischenreinsetzen kann, ohne die schon vorhanden Kette groß verändern zu müssen. iIn der endgültigen Variante kann man natürlich dann verschiedene Pipe-Stufen "wegkürzen".

lks
Guenni
Guenni 10.03.2013 aktualisiert um 17:45:02 Uhr
Goto Top
Hi ottscho,

sofern nur die Importroutine das Timeout-Problem verursacht:

- Definiere einen Zähler für alle Zeilen
- Definiere einen Wert für die Zeilen, die pro Import importiert werden sollen

Wenn der Zähler_für_alle_Zeilen modulo_dividiert Zeilen_pro_Import 0 ergibt . . .

- Importroutine aufrufen
- Zähler_für_alle_Zeilen auf 0 setzen

Anschließend werden die letzten Zeilen eingelesen(importiert).

Beispiel:

<?php
$lines=0;
$max_lines=500;
$f=fopen("doku.txt","r");  
	while($line=fgets($f)){
	 $line_arr[$lines]=$line;
	 $lines++;
		if($lines%$max_lines==0){
		 echo "// Importroutine: ";  
		 echo $lines.' Zeilen werden importiert.<br>';  
			$lines=0;
		}
	}
	/*
	* Restliche Zeilen importieren
	*/
	while($line=fgets($f)){
	 $line_arr[$lines]=$line;
	 $lines++;
	}
	if($lines>0){
	 echo "// Importroutine: ";  
	 echo $lines.' Zeilen werden importiert.<br>';  
	}
fclose($f);
?>

Gruß
Günni
ottscho
ottscho 11.03.2013 um 08:04:00 Uhr
Goto Top
Moin Zusammen,

danke noch mal für die vielen Inputs von euch.
Auf die Schnell konnte ich es nicht über eine Consolen Anwendung umsetzen. Problem ist, dass die PHP Datei innerhalb eines Plugin System läuft und die API der Shopschnittstelle dort aus einer anderen Classe geladen wird.
Dazu fehlt mir einfach das Knowhow.

Ich habe es nun mit einem Byte Datenzeiger umgesetzt (fseek). Somit fange ich immer genau an der Stelle an zu lesen, wo ich davor aufgehört habe. D.h. ich muss nicht immer alle Datensätze überspringen.

Als Consolen Script muss es aber früher oder später laufen. Da später mehrere Shops damit bedient werden sollten.
Dann ist es über ein PHP Script, welches über eine URL aufgerufen wird mit der Zeit zu unübersichtlich.

Danke euch...