[MySQL] Race Conditions

chrissel

Woohooo!
ID: 211634
L
20 April 2006
4.489
472
Leider gibt es ja in MySQl das Problem mit Race Conditions.
Ich habe nun eine mögl. Lösung so im Kopf.
Was denkt ihr? Würde die gehen? Kennt ihr eine bessere?

Mein Gedankengang:
Man erstellt zwei Textdateien.
Eine nennt man z.B. "lprozess.txt" und die andere "aprozess.txt"
Nun gibt man jeder MySQL Abfrage eine "ProzessID" (die letzte wird dann aus "lprozess.txt" ausgelesen und die Zahl in "lprozess.txt" wird um eins erhöht).
Dann macht man eine Schleife (mit sleep() z.B. drin) die solange läuft, bis die Zahl in "aprozess.txt" die gleiche ist wie die "ProzessID" der Abfrage.
Wenn es die gleiche ist wird die MySQL-Abfrage ausgeführt und die Zahl in "aprozess.txt" wird um eines erhöht.
Dieses passiert dann halt für jede MySQL-Abfrage.

Meine Befürchtung:
Das ganze Script wird dadurch mega langsam, da für jede MySQL-Abfrage Xmal die "aprozess.txt" ausgelesen werden muss.
Und die Serverauslastung würde dadurch auch steigen.
 
Das Problem mit den Race-Conditions hast du ja nur bei sehr großen Projekten (ich denk jetzt einfach mal das es um PHP geht).

Und du hast mit der Vermutung das es langsam werden wird vermutlich mehr als Recht.


Sie dir dazu mal diese Storage-Engine an, die Lösung deines Problems ist nämlich eigentlich DB-Angelegenheit:

https://de.wikipedia.org/wiki/InnoDB
 
so wirklich langsam sollte das nicht werden, das währen ja nur zwei-drei befehle mehr pro abfrage. (also wenn man net gerate 100000 abfragen pro sekunde braucht).

pro query-start sperrst du die datei, weitere zugreifende anfragen registrieren das und warten in einer schleife bis der zugriff wieder möglich wird. es gibt nur ein problem, wenn durch nen dummen zufall der bearbeitungsprozess abbricht und die sperre erhalten bleibt.. das wäre dann der schlimmste aller fälle :)

ansonsten hat scriper natürlich recht. ;)
 
joah, is aber n ziemlicher Blödsinn noch ne 2. "Gefahrenquelle" einzubauen, außerdem wird die Festplatte des Servers doppelt "belästigt" (wenns der selbe Server ist) uswusf....kann bei einem vollgestopften shared-hosting-Paket schon mal eng werden...
 
auch wenn man keine optimale performance erreichen wird, kann es bei der erstellung eines freehoster tauglichem script durchaus eine relevante alternative darstellen. *hm..

ansonsten hast du natürlich recht.
 
so wirklich langsam sollte das nicht werden, das währen ja nur zwei-drei befehle mehr pro abfrage. (also wenn man net gerate 100000 abfragen pro sekunde braucht).

Warum nur 2-3 Abfragen?
Für jeden Durchgang der Schleife, wird ja die Datei "aprozess.txt" neu ausgelesen ;)

pro query-start sperrst du die datei, weitere zugreifende anfragen registrieren das und warten in einer schleife bis der zugriff wieder möglich wird. es gibt nur ein problem, wenn durch nen dummen zufall der bearbeitungsprozess abbricht und die sperre erhalten bleibt.. das wäre dann der schlimmste aller fälle :)

Wie meinst du das mit dem Sperren?
Das mit der ProzessID einfach alles weglassen, und dann bei jeder Abfrage gucken ob Datei beschreibbar ist, wenn ja Abfrage ausführen, Datei sperren und nach der Abfrage Datei entsperren?
Aber da hast du Recht, dass das Script nicht mehr laufen würde wenn der Bearbeitsungsprozess abbricht....
Hmm :roll: :roll:
 
natürlich pro schleifendurchlauf.

für zwei-drei blicke findest du hier eine passende idee: https://php.net/flock ;)

da gibts es sogar schon die passende helfer-klasse.

wenn man die nutzung dieses features auf nur geringe bereiche einschränken könnte, z.b für spezielle auswertungen im acp, könnte man das schon machen. da dem admin dann spezielle features bereitgestellt werden könnten, um dieses problem zu lösen. aber nun ja.. das war nur ein beispiel für den worst-case.
 
bietet nicht auch mysql transaktionen an, indem man das autocommit deaktiviert und manuell mit commit arbeitet? damit dürfte doch dein problem behoben sein, oder?
 
:-/ wieso so umständlich. Entweder du nimmst wie schon vorgeschlagen einfach Transaktionen, oder wenn du Myisam nutzt sperrst du einfach die Tabelle.

PHP:
LOCK TABLES table WRITE;
//hier gehöhrt die tabelle ganz dir
//...
//...
UNLOCK TABLES;

Ganze Tabellen zu sperren solltest du aber behutsam einsetzen. Alle anderen Queries die die Tabelle brauchen warten bis das write lock wieder aufgehoben ist. Im schlimmsten fall kannst du damit den Server lahmlegen. Einmal wegen zuvielen zugriffen und zulange lock zeiten, andererseits durch race conditions ;) (indem du mehrere Tabellen sperst) Also VORSICHTIG damit umgehen, ansonsten Transaktionen verwenden die können Locks auf Zeilenebene ausführen und erkennen auch race conditions.

*edit* Achja, alternativ kannst du auch race conditions in der Anwendung abfangen. Wenn man von den Beispiel auf Wikipedia ausgeht könnte man einfach beim Schreiben prüfen ob der Wert noch Orginal ist.

PHP:
$wert = SELECT wert FROM table;
$new_wert = irgendwas;
UPDATE table SET wert = $new_wert WHERE wert = $wert;
Wenn ein Datensatz geändert wurd ist alles OK, ansonsten wurde der Orignalwert zwischen auslesen und schreiben geändert. -> worrauf du an der stelle reagieren könntest
 
Ich möchte noch 2 weitere Möglichkeiten für ein Schutz gegen Race-Conditions in den Raum werfen:

1. bedingte Updates
Bei dieser Methode kann man davon ausgehen, dass eine Spalte in der Datenbank zB. nie einen negativen Wert haben soll so liese sich zB. folgendes machen:
Code:
UPDATE `table` SET `spalte`=`spalte`-5 WHERE `id`=7 and `spalte`>=7
wir könnenüber diese Methode viele gleichzeitige Zugriffe haben, ohne dass es zu Race-Conditions bei Integern kommt.

2. Row-Level-Locking in MyIsam
wau? Row-Level-Locking in MyIsam ist möglich? das kann doch eigentlich nur InnoDB, MyIsam verwendet doch ein kompletten Table Lock.
Ich muss zugeben, es ist kein richtiges Row-Level-Locking wir können aber über einen MySQL regeln, dass immer nur eine MySQL-Vrbindung gleichzeitg das Recht auf einen String hat und einen Lock für diesen anfordert.
So ist es möglich mit der MySQL-Funktion GET_LOCK() ein Row-Level-Locking-System zu simlieren.
 
Das mit GET_LOCK() verstehe ich noch nicht so ganz.
Wäre das so z.B. richtig?

Code:
GET_LOCK('spalte',10);
UPDATE `tabelle` SET `spalte`=`spalte`-1000;
RELEASE_LOCK('spalte');
 
Und wie würde das gehen?^^
neija du blockierst nicht eine spalte sondern einen datensatz den du genau identifizieren kannst (primary key, unique id)


Das Lock einfach entsprechend benennen. Denke daran, das ist keine wirkliche Sperre, sondern blockiert nur eine Zeit GET_LOCK('equalstring',x) - so habe ich es zumindest aus der manual verstanden ;)
jup, aber wenn man 10sek einträgt sollte das schon reichen, wenn nen query in nem produktiven system länger als 10sec benötigt (selbst unter den größten peaks) dann stimmt da was gewaltig nicht^^
 
neija du blockierst nicht eine spalte sondern einen datensatz den du genau identifizieren kannst (primary key, unique id)[..]

Und wie kann ich einen Datensatz genau identifizieren?
Kann man z.B. GET_LOCK('SELECT `id` FROM `tabelle` WHERE `id`=5', 10); schreiben? Oder was?^^
Und wie ist das wenn nun Scriptausführung X den Datensatz sperrt und dann Scriptausführung Y GET_LOCK(); ausführt? Denn GET_LOCK(); entblockt ja auch alle Datensätze wenn sie geblockt wurden.
 
Und wie kann ich einen Datensatz genau identifizieren?
Kann man z.B. GET_LOCK('SELECT `id` FROM `tabelle` WHERE `id`=5', 10); schreiben? Oder was?^^
Und wie ist das wenn nun Scriptausführung X den Datensatz sperrt und dann Scriptausführung Y GET_LOCK(); ausführt? Denn GET_LOCK(); entblockt ja auch alle Datensätze wenn sie geblockt wurden.

wie wäre es mit einem GET_LOCK('5',10)?
Immerhin willst du ja den Datensatz mit der id 5 sperren.
GET_LOCK blockiert solange bis der timeout erreicht wird oder eine andere mysql-verbindung den lock wieder freigibt ;)
 
wie wäre es mit einem GET_LOCK('5',10)?
Immerhin willst du ja den Datensatz mit der id 5 sperren.

GET_LOCK('string', 10); bedeutet alle Abfragen in denen string drin vorkommen sind für 10 Sek. gesperrt oder was?
Den ersten Parameter verstehe ich noch nicht so ganz^^

GET_LOCK blockiert solange bis der timeout erreicht wird oder eine andere mysql-verbindung den lock wieder freigibt ;)

Geht das nicht nur bei der gleichen Verbindung? Weil sonst würde das ja nichts bringen.
 
GET_LOCK('string', 10); bedeutet alle Abfragen in denen string drin vorkommen sind für 10 Sek. gesperrt oder was?
Den ersten Parameter verstehe ich noch nicht so ganz^^
lesen sollte bilden ;)
es wird gar kein query geblockt, du setzt nur eine sperre auf diesen string, weshalb eine andere db-verbindung nicht die gleiche sperre anlegen kann


Geht das nicht nur bei der gleichen Verbindung? Weil sonst würde das ja nichts bringen.
öhm, ich habe doch explizit geschrieben, andere db-verbindungen, die gleiche würde ja mal überhaupt keinen sinn machen