Seite 1 von 1
Parallene Scriptausführung (mysql Problem)
Verfasst: 08.09.2007, 18:31
von pvdb
Hi,
es geht darum, dass preise und bestände eines lieferanten in einen online shop automatisch eingelesen werden. die daten sollen effizient aktualisiert werden. somit hat meine tabelle das feld status. wenn dieser den wert 0 hat, muss der datensatz im shop aktualisiert werden. nachdem ich den datensatz eingelesen habe, setze ich status auf 1, damit ein script welches paralell läuft das gleiche produkt nicht auch aktualisiert. hier mein php ausschnitt in verkürzter fassung:
Code: Alles auswählen
while (true)
{
// nächstes relevante produkt zum updaten einlesen (weches sich verändert hat und bereits am längsten wartet)
$sql = "select id, art_id from SUPPLIERS where status = 0 order by update_time asc limit 1";
$res = mysql_query($sql);
$num = mysql_num_rows($res);
// wenn alle produkte durch sind, abbrechen
if ($num == 0)
break;
// produkt einlesen
$row = mysql_fetch_assoc($res);
$id = $row['id'];
$art_id = $row['art_id'];
// datensatz als bearbeitet markieren, damit es im falle von mehreren threads nicht erneut bearbeitet wird
$sql = "update SUPPLIERS set status = 1 where id = $id";
mysql_query($sql);
// daten verarbeiten
update_product($art_id);
echo $art_id . " verarbeitet\n";
}
das script läuft soweit einwandfrei. nun habe ich das script mal doppelt ausgeführt und das problem ist, dass die scripte so zwischen 20 und 30 datensätzen automatisch abbrechen.
wenn die einzeln nacheinander laufen, läuft alles problemlos, sind auch einige tausend datensätze die angepasst werden müssen, also kein grund vorzeitig abzubrechen.
denke mal es liegt daran dass ein wert liest bevor der andere was geschrieben hat, wobei ich nicht genau nachvollziehen kann, wieso der einfach irgendwann abbricht.
gibt es eine möglichkeit dagegen etwas zu tun, ohne großartig ressourcen zu verschwenden? die tabelle läuft auf myisam und innodb kommt leider aus schlechten erfahrungen und unwissen nicht in frage.
phil
Verfasst:
von
SEO Consulting bei
ABAKUS Internet Marketing Erfahrung seit 2002
- persönliche Betreuung
- individuelle Beratung
- kompetente Umsetzung
Jetzt anfragen:
0511 / 300325-0.
Verfasst: 08.09.2007, 19:28
von pvdb
wenn ich vor der ersten sql abfrage ein
usleep(50);
setze, gibt es bei meinen versuch bei den ersten 250 datensätzen keine probleme. allerdings werde ich mich darauf sicherlich nicht verlassen können, dass es immer einwandfrei läuft.
macht es sinn die tabelle mit den mysql funktionen irgendwie zu sperren. schaut es so aus das andere scripts dann nicht zugreifen können und fehler geben oder warten die solange bis die tabelle wieder frei ist?
was würdest ihr machen, vielleicht das ganze ganz anders lösen?
phil
Re: Parallene Scriptausführung (mysql Problem)
Verfasst: 08.09.2007, 23:37
von nerd
pvdb hat geschrieben:
denke mal es liegt daran dass ein wert liest bevor der andere was geschrieben hat, wobei ich nicht genau nachvollziehen kann, wieso der einfach irgendwann abbricht.
kann hoechstens passieren wenn du dir einen deadlock baust.
versuch mal beim ersten select alle werte in ein array zu lesen, dann die werte aus dem array herraus zu bearbeiten und dann erst ein update zu machen.
Verfasst: 09.09.2007, 01:52
von To-Bi-As
Also verstehe ich das richtig?
Du frägst erst mal Deine DB nach einem Datensatz mit Status 0 ab.
Diesen änderst Du dann auf Status 1.
Die update_product($art_id); macht was?
Daten von externer DB abholen und bei Dir eintragen, also ein externes Select und ein lokales Update?
Für den Fall dass das so ist, dann kann es sein dass es bei Deinem Lieferanten eine Sperre gibt bei der Anzahl der Anfragen pro Zeiteinheit.
Lass solche Sachen wie Limit 1 weg. Pack die komplette SQLs als String zusammen und sende dann eine SQL-Anfrage. Das ganze für Dein lokales Select, das lokale Update, die Abfragen beim Lieferanten und das lokale Update bei Dir.
Dann denke ich mal dass es gehen sollte. Ich glaube nämlich nicht dass sich Dein Lieferant freut wenn da binnen kürzester Zeit tausende Anfrage kommen und jedesmal ein Datensatz geholt wird.
Kann mich aber auch irren.
Gruß, Ingo
Verfasst: 09.09.2007, 09:48
von pvdb
@nerd
wenn ich die daten in ein array einlese stehen die infos ja nicht in beiden scripts zur verfügung und wenn jedes script (also jede instanz) sein eigenes array hat hilft es auch nicht. ich denke mal du meinst, das ich ein script habe mit dem array und dann mittels php nohup eigene threads starte die das verarbeiten. dies wollte ich ungern machen, da ich unter os x probleme habe das zuverlässig zum laufen zu kriegen.
@To-Bi-As
es ist so, dass die preise und bestände aller lieferanten bereits in meiner eigener SUPPLIERS tabelle drinnen sind. (kann sich also kein lieferant beschweren. lieferantendaten werden immer komplett eingelesen)
nun geht es darum preise und bestände meiner eigenen lieferanten tabelle SUPPLIERS im shop zu updaten. dies wollte ich so bauen, dass php scripts parallel laufen können, damit es schneller geht.
deswegen auch die while endlosschleife schleife, die solange läuft wie es produkte gibt die aktualisiert werden müssen. ich lese den ersten datensatz und setze diesen sofort als bearbeitet. dadurch soll sichergestellt werden, dass ein paralleles script dieses produkt nicht selbst verarbeitet und ich genügend zeit habe die neuen daten in die shop tabellen zu spielen (update_product).
Verfasst: 09.09.2007, 11:41
von pvdb
und das komische ist, dass es manchmal überhaupt keine probleme gibt. habe z.b. 4 instanzen ausführen lassen, die je 500 datensätze verarbeiten sollten und nichts bricht vorzeitig ab. dennoch werden viele datensätze doppelt verarbeitet

Verfasst: 09.09.2007, 13:59
von pvdb
habe nun einfach im ersten sql ein order by rand() drinnen. dann ist es zwar nicht mehr nach dem lifo prinzip, es werden aber überwiegend unterschiedliche datensätze verarbeitet, wohl die einfachste lösung

Verfasst: 09.09.2007, 14:04
von mcchaos
pvdb hat geschrieben:dennoch werden viele datensätze doppelt verarbeitet

Klar werden sie. Ist eines der klassischen Probleme beim Multitasking:
Thread 1 holt sich einen Datensatz. Direkt nach dem Holen, also noch bevor status=1 gesetzt werden kann, bekommt Thread 2 CPU-Zeit. Welcher Datensatz wird nun von Thread 2 geholt? Natürlich der gleiche wie in Thread 1, denn das ist immer noch der älteste mit status=0.
Was macht denn nun update_product($art_id)? Davon wäre es abhängig, ob es überhaupt Sinn ergibt, das Ganze parallel laufen zu lassen. Denn der Rest (Datensatz holen, kurz was machen, Datensatz updaten) bringt parallel nicht viel, da der Flaschenhals hier wohl der DB-Zugriff sein wird.
Du könnest vor dem Select bis nach dem Update alles mit LOCK_TABLE oder GET_LOCK sperren. Allerdings bist Du dann höchstwahrscheinlich mit einer sequentiellen Abarbeitung wirklich schneller.
Verfasst: 09.09.2007, 14:06
von mcchaos
pvdb hat geschrieben:habe nun einfach im ersten sql ein order by rand() drinnen. dann ist es zwar nicht mehr nach dem lifo prinzip, es werden aber überwiegend unterschiedliche datensätze verarbeitet, wohl die einfachste lösung

Das ist dann aber wirklich nur RAND, also Zufall

Verfasst: 09.09.2007, 14:58
von pvdb
ja, aber der zufall tut seinen zweck

Verfasst: 09.09.2007, 18:39
von SebaF
durch rand verschwendest Du sehr viel Rechenleistung, da jedesmal temporär eine neue Tabelle aufgebaut wird.
Statt dessen würde ich mit Transaktionen arbeiten (benötigt innodb) und gleich 50 Datensätze oder ähnliches holen, bearbeiten und zurückschreiben.
Verfasst: 11.09.2007, 06:09
von Outman
Morgen,
also ich würde es so machen.
Code: Alles auswählen
<?
// nächstes relevante produkt zum updaten einlesen (weches sich verändert hat und bereits am längsten wartet)
$sql = "select id, art_id from SUPPLIERS where `status` = '0' order by update_time asc";
$res = @mysql_query($sql) or die('Fehler im Select SQL');
$num = mysql_num_rows($res);
if($num != 0){
while ($row=mysql_fetch_array($res)){
$id = $row['id'];
$art_id = $row['art_id'];
// datensatz als bearbeitet markieren, damit es im falle von mehreren threads nicht erneut bearbeitet wird
$sql = "update SUPPLIERS set `status` = '1' where `id` ='".$id."' AND `status` = '0' LIMIT 1";
@mysql_query($sql) or die('Fehler im Update SQL');
// daten verarbeiten
update_product($art_id);
echo $art_id . " verarbeitet\n";
}
} else echo 'Keine Daten vorhanden!';
?>
Verfasst: 11.09.2007, 12:10
von net(t)worker
naja... ich nutze für solche Dinge eine technik, die ich "Datensatz schiessen" nenne...
jede Instanz des scriptes wird mit einer anderen id gestartet... bevor ich anfange setze ich in der Table per update ein feld auf 0....
dann schiesst jedes script einfach nen update in die Daten rein:
Update table set feld=$scriptid where feld=0 limit 1
so wird also 1 Datensatz "geschossen"... dieser kann dann einfach per
Select * from table where feld=$scriptid limit 1;
geholt werden....
wenn dann keines gefunden wurde, kann das script beendet werden, da alle datensätze bearbeitet wurden...
wenn ein datensatz geholt werden konnte wird dieser bearbeitet und das feld dann auf 9999 gesetzt, so dass es als bearbeitet gekennzeichnet ist....
alles verstanden?