"SVERWEIS" per PHP auf Geschwindigkeit trimmen

TOMahawk85

Erfahrenes Mitglied
Hallo,

Ich arbeite bei einem kleinen Onlineversandhändler, der (noch) ohne Warenwirtschaftsystem agiert. Um uns die Arbeit trotzdem zu erleichten, bin ich dabei eine Art WWS Light per PHP zu programmieren. Das soll Warenbestände (also Ware ist oder ist nicht verfügbar) aus den Bestandslisten unserer Lieferanten in eine SQL-Datenbank eintragen, damit wir damit arbeiten können. Im moment läuft noch alles über EXCEL-Listen, was sehr unschön zum Arbeiten ist.

Zum eigentlichen Problem:
Um die Bestände in die Datenbank einzutragen, muss logischerweise die Artikelnummer der Lieferantenliste (CSV-Format) mit der in unserer Datenbank verglichen und der zugehörige Warenbestand übernommen werden.
Das zu programmieren war nicht das Problem. Für "kleine" Listen mit um die 10.000 Zeilen funktioniert das auch. Werden die Listen aber deutlich länger (und die Dauer der Verarbeitung steigt ja exponentiell), kommt es irgendwann zu folgender Fehlermeldung:
"Service Unavailable
The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.
Additionally, a 503 Service Unavailable error was encountered while trying to use an ErrorDocument to handle the request."

Das will ich natürlich vermeiden. Deswegen meine Frage:
Wie kann/muss ich folgenden Quellcode optimieren, damit auch größere Listen problemlos abgearbeitet werden?

Erledigt werden soll das am Ende übrigens per "shell_exec", was auch bereits funktioniert, aber zum selben Problem führt.
Über zusätzliche Optimierungsvorschläge wäre ich natürlich nicht undankbar.
"max_execution_time" ist übrigens ein ganzer Tag.

PHP:
uploadFile('temp/', $_FILES['fileUpload']["name"], 'fileUpload'); //Upload Lieferantenliste
$supplierNameIntern = $_POST['selectImport']; //Interner Name der Lieferanten

// Auslesen aller relevanten Daten des ausgewählten Lieferanten
$supplier = readDatabase('SELECT * FROM admin_suppliers WHERE supplier_name_intern="'.$supplierNameIntern.'"');
$targetNumber = $supplier['supplier_row_manufacturer_number'];
$rowQuantity = $supplier['supplier_row_quantity'];
$targetRow = $supplier['supplier_number_type'];
$supplierName = $supplier['supplier_name'];
$valueInStock = explode(";", $supplier['supplier_value_in_stock']);
$valueLimitedStock = explode(";", $supplier['supplier_value_limited_stock']);
$valueNotInStock = explode(";", $supplier['supplier_value_not_in_stock']);

// Daten der Lieferantenliste und der Datenbank in Array packen
$array = readCsv('temp/',$_FILES['fileUpload']["name"], ';', true);
$sqlArray = readDatabase("SELECT * FROM admin_list_content WHERE supplier='".$supplierName."'");

// Tabellentitel für "REPLACE"-Query auslesen
$tableNames = readDatabase('SHOW COLUMNS FROM admin_list_content');
foreach($tableNames as $title){ $columns .= $title['Field'].', '; }
$columns = substr($columns,0,-2);

// Lieferantenliste-Array mit Datenbank-Array vergleichen
$n=0;
foreach($sqlArray as $var){
    
    // Suche nach "Zeile"/2. Ebene im Array
    $key = searchForId($var['supplier_number'], $targetNumber, $array);
    $supplierQuantity = $array[$key][$rowQuantity];
    
    // Werte "Lieferbar", "Begrenzt lieferbar", "Nicht lieferbar" oder, falls nicht anders bei Lieferant angegeben, konkreter Warenbestand
    switch(true){
        case (in_array($supplierQuantity,$valueInStock)): $quantity = 100;
            break;
        case (in_array($supplierQuantity,$valueLimitedStock)): $quantity = 1;
            break;
        case (in_array($supplierQuantity,$valueNotInStock)): $quantity = 0;
            break;
        default: $quantity = $supplierQuantity;
    }
    
    // Erstellung des Query pro Datenbankzeile
    foreach($var as $value){
        $content .= "'".$value."', ";
    }
    editDatabase('REPLACE INTO admin_list_content ('.$columns.') VALUES ("'.implode('","', $sqlArray[$n]).'");');
    $n++;
    
    unset($content);
}

Und hier noch die Funktion "searchForId":
PHP:
function searchForId($id, $fieldName, $array) {
   foreach ($array as $key => $val) {
       if ($val[$fieldName] === $id) {
           return $key;
       }
   }
   return null;
}

Alle anderen eingebauten Funktionen sind für das Geschwindigkeitsproblem praktisch nicht relevant und somit auch nicht angegeben.

Ich hoffe mir kann hierbei geholfen werden und bedanke mich schonmal im Vorraus für die Hilfe.

Mit freundlichen Grüßen,
TOMahawk85
 
Klingt für mich nach einem Timeout. Was passiert wenn du diesen hochsetzt

PHP:
<?php
    set_time_limit(2400); //2400 Sekunden


     //..Dein Code
 
@Kalito
set_time_limit hat leider nicht geholfen, auch nicht mit deutlich höheren Werten.


Wie kommst du da drauf? Hast du die einzelnen Teile gemessen?

Grüsse,
BK
Ich habe die Dauer der einzelnen Funktionen nicht gemessen.
Die Funktionen werden alle nur einmal aufgerufen und beinhalten außer der "readCsv", die die CSV-Daten in ein Array packt, keine Schleifen. Und letztere geht selbst mit weit über 60.000 Zeilen und inklusive SQL-REPLACE-Query rasend schnell.


Das Zeitaufwand liegt (soweit ich feststellen konnte) wirklich darin, die Artikelnummern aus 2 Arrays zu vergleichen und den Bestand von einem Array in das andere zu übertragen. Dafür bräuchte ich halt eine Lösung.
Eine Möglichkeit den Abbruch des ganzen zu verhindern oder wenigsten hinauszuzögern, wäre natürlich wünschenswert.
 
Könnte es nicht eventuell am Cache liegen?

Hast du mal alle Teile debugged?
Um zu prüfen ab wann er sich quasi aufhängt?

Es könnte ja auch am readCSV liegen. Zumindestens Vermute Ich es mal :)

Spontan könnt man dies testen mit fgets :)
 
Eine Möglichkeit, die Performance zu optimieren, ist, das Ganze mit der Datenbank abzuwickeln und jeweils einen Index auf die Spalte anzulegen, auf die häufig zugegriffen wird. Um davon profitieren zu können, könnte es sinnvoll sein, auch den Inhalt der CSV-Datei in die Datenbank einzutragen. Dann entsteht zwar zunächst zusätzlich der Aufwand dafür einschl. Anlegen des Index, aber nur einmal, die späteren Zugriffe auf wesentlich mehr als 10000 Datensätze sind optimiert.

Oder eine Möglichkeit, die sehr schnell zu testen ist: Beim Iterieren über admin_list_content nicht den Umweg über ein Array gehen, sondern direkt über das Ergebnis der Datenbankabfrage iterieren.

Und, ebenfalls relativ schnell zu implementieren und zu testen: Das Array aus den CSV-Daten so umorganisieren, dass ein einfacher Schlüsselzugriff möglich ist:
Code:
function searchForId($id, $array) {
   if (isset($array[$id])) return $array[$id];
   return null;
}
 
Zuletzt bearbeitet:
Danke erstmal für die Antworten, für die ich zum Teil etwas mehr Zeit brauche als ich momentan habe.

@EuroCent
readCsv ist, wie ich bereits gepostet habe nicht das Problem. Das geht selbst mit 60000-70000 Zeilen in 2-3 Sekunden.
Und ich muss zu meiner Schande gestehen, dass ich bis heute nie Debugged habe (und mich damit auch nicht auskenne). Ich lasse mir immer nur an bestimmten Werte ausgeben, um festzustellen, wo es hängt (was mir hierbei nicht wirklich hilft).

Was ich aber feststellen konnte, ist, dass es scheinbar an der bereits geposteten Funktion "searchForId" liegt.
Wenn ich einfach ein $key festlege und die Funktions aufrufe, ist die Schleife nach 4-5 Sekunden durchgearbeitet.
PHP:
function searchForId($id, $fieldName, $array) {
   foreach ($array as $key => $val) {
       if ($val[$fieldName] === $id) {
           return $key;
       }
   }
   return null;
}
Das heißt, ich brauche eine Möglichkeit, so schnell wie möglich herauszufinden, wo in einem Array (multidimensional) ein bestimmter Wert (Artikelnummer) zu finden ist.
Gibt es da eine mir unbekannte php-Funktion, die das erfüllt?
Oder muss man zwingend den Weg über eine Schleife gehen?
 
Hi,

du könntest auch mit PHP Funktionen die Suche machen lassen, dies sollte performanter sein als eine Eigenimplementierung:

PHP:
$key = array_search($id, array_column($array, $fieldName));

Quelle: https://stackoverflow.com/a/34785508 und https://stackoverflow.com/a/24527099

Du könntest aber auch vorgängig dein $array einmalig durchgehen und dein mapping aufbauen. Anschliessend (in der Schleife) kannst du dann mit dem Cache arbeiten und musst nicht immer wieder neu suchen.

Grüsse,
BK
 
Zuletzt bearbeitet:
Ich habe mal die Variante von Bratkartoffel mit meiner verglichen:
Code:
for ($i = 0; $i < 200000; $i++) {
    $array[$i] = ['field1'=>'val1' . $i,'field2'=>'val2' . $i,'field3'=>'val3' . $i,'field4'=>'val4' . $i];
}
echo microtime();
$record = array_search('val4180000', array_column($array,'field4'));
echo microtime();
var_dump($record);
var_dump($array[$record]);
foreach($array as $rc) {
    $arr2[$rc['field4']] = $rc;
}
// var_dump($arr2);
echo microtime();
$rec2 = $arr2['val4180000'];
echo microtime();
var_dump($rec2);
Ausgabe:
0.60742300 15287977790.64242500 1528797779D:\Gemeinsame Dateien\Webentwicklung\thread56-tomahawk.php:612:int 180000

D:\Gemeinsame Dateien\Webentwicklung\thread56-tomahawk.php:613:
array (size=4)
'field1' => string 'val1180000' (length=10)
'field2' => string 'val2180000' (length=10)
'field3' => string 'val3180000' (length=10)
'field4' => string 'val4180000' (length=10)

0.73143000 15287977790.73143000 1528797779D:\Gemeinsame Dateien\Webentwicklung\thread56-tomahawk.php:621:
array (size=4)
'field1' => string 'val1180000' (length=10)
'field2' => string 'val2180000' (length=10)
'field3' => string 'val3180000' (length=10)
'field4' => string 'val4180000' (length=10)
 
Zurück