doppelte SQL-Abfrage 100%-tig verhindern - Gewinnspiel
doppelte SQL-Abfrage 100%-tig verhindern - Gewinnspiel
Ich möchte ein Gewinnspiel zu Ostern machen, in dem der User auf eine Seite kommen muss. Der erste, der die Seite erreicht gewinnt. Aber was auf keinen Fall passieren darf ist, dass zwei User gleichzeitig gewinnen.
Was wäre da die beste Technik?
Erst ein SELECT und dann ein UPDATE ist denke ich zu unsicher. Weil es theoretisch möglich wäre, dass das UPDATE noch nicht fertig ist, so dass mehrere User vorher noch SELECT schaffen und demnach auch das UPDATE erfolgreich durchgeführt werden könnte.
Daher dachte ich an folgendes:
DELETE WHERE gewinn_id = 1
Also wenn DELETE erfolgreich durchgeführt wurde (kann man ja mit TRUE / FALSE abfragen), dann hat der User als Erster die Seite erreicht. Alle die danach kommen dürften demnach nur noch ein FALSE erhalten. Ist es da trotzdem möglich, dass ein 100% synchroner Aufruf mehrere TRUE zurückgibt oder kann ich das 100%-tig ausschließen?
Welche Lösung könnte es noch geben?
Gruß
Was wäre da die beste Technik?
Erst ein SELECT und dann ein UPDATE ist denke ich zu unsicher. Weil es theoretisch möglich wäre, dass das UPDATE noch nicht fertig ist, so dass mehrere User vorher noch SELECT schaffen und demnach auch das UPDATE erfolgreich durchgeführt werden könnte.
Daher dachte ich an folgendes:
DELETE WHERE gewinn_id = 1
Also wenn DELETE erfolgreich durchgeführt wurde (kann man ja mit TRUE / FALSE abfragen), dann hat der User als Erster die Seite erreicht. Alle die danach kommen dürften demnach nur noch ein FALSE erhalten. Ist es da trotzdem möglich, dass ein 100% synchroner Aufruf mehrere TRUE zurückgibt oder kann ich das 100%-tig ausschließen?
Welche Lösung könnte es noch geben?
Gruß
meine Foren: http://www.maxrev.de/communities.htm
Ich kaufe Dein Forum! Angebote bitte an marc at gutt punkt it
Ich kaufe Dein Forum! Angebote bitte an marc at gutt punkt it
Um so was sauber zu machen, gibt's die Transaktionen bei SQL: http://dev.mysql.com/doc/refman/5.0/en/ ... mands.html und http://dev.mysql.com/doc/refman/5.0/en/ ... model.html
Dazu brauchst du jedoch MySQL-Tabellen, die das auch unterstützen (InnoDB) und musst auch beim Programmieren aufpassen, dass du keinen Autocommit machst (http://de3.php.net/manual/en/function.m ... commit.php - nur mit MySQLi und PHP 5)
Dann machst du einen SELECT, fügst ihm aber einan. (siehe http://dev.mysql.com/doc/refman/5.0/en/select.html und http://dev.mysql.com/doc/refman/5.0/en/ ... reads.html)
Anschließend führst du den Update-Befehl durch. Zwischen den zwei Vorgängen sperrt MySQL den Datensatz (nur den - nicht die ganze Tabelle) für Lesezugriffe. So mit kann genau das Problem nicht auftreten.
Gruß, Philipp
Dazu brauchst du jedoch MySQL-Tabellen, die das auch unterstützen (InnoDB) und musst auch beim Programmieren aufpassen, dass du keinen Autocommit machst (http://de3.php.net/manual/en/function.m ... commit.php - nur mit MySQLi und PHP 5)
Dann machst du einen SELECT, fügst ihm aber ein
Code: Alles auswählen
FOR UPDATE
Anschließend führst du den Update-Befehl durch. Zwischen den zwei Vorgängen sperrt MySQL den Datensatz (nur den - nicht die ganze Tabelle) für Lesezugriffe. So mit kann genau das Problem nicht auftreten.
Gruß, Philipp
Kein Support per PN!
Der Sozialstaat ist [...] eine zivilisatorische Errungenschaft, auf die wir stolz sein können. Aber der Sozialstaat heutiger Prägung hat sich übernommen. Das ist bitter, aber wahr. (Horst Köhler)
Meine Mods
Der Sozialstaat ist [...] eine zivilisatorische Errungenschaft, auf die wir stolz sein können. Aber der Sozialstaat heutiger Prägung hat sich übernommen. Das ist bitter, aber wahr. (Horst Köhler)
Meine Mods
Und ein DELETE könnte demnach 2x ausgeführt werden?
Also das wollte ich zu erst machen: (Starteinstellung)
INSERT INTO gewinnspiel (gewinn_id) VALUES (12)
Dann kommen die ganzen Besucher auf gewinn.php?gewinn_id=12 und dieser Befehl wird bei jedem Besucher ausgelöst:
SELECT gewinn_id FROM gewinnspiel WHERE gewinn_id = 12
wenn das erfolgreich war mache ich das:
DELETE FROM gewinnspiel WHERE gewinn_id = 12
wenn das erfolgreich war, dann weiß ich, dass derjenige gewonnen hat.
Theoretisch dachte ich mir, dass eigentlich nur der erste Besucher diesen Befehl abarbeiten kann. Schließlich werden doch mysql-Befehle einer nach dem anderen abgearbeitet?!
Das mit dem UPDATE != verstehe ich nicht. Das betrifft doch automatisch alle anderen Zeilen, die in der Tabelle sind und nicht auf "neuer Wert" lauten. Demnach könnte ich doch nur ein Gewinnspiel pro Tabelle machen. Richtig?
Also das wollte ich zu erst machen: (Starteinstellung)
INSERT INTO gewinnspiel (gewinn_id) VALUES (12)
Dann kommen die ganzen Besucher auf gewinn.php?gewinn_id=12 und dieser Befehl wird bei jedem Besucher ausgelöst:
SELECT gewinn_id FROM gewinnspiel WHERE gewinn_id = 12
wenn das erfolgreich war mache ich das:
DELETE FROM gewinnspiel WHERE gewinn_id = 12
wenn das erfolgreich war, dann weiß ich, dass derjenige gewonnen hat.
Theoretisch dachte ich mir, dass eigentlich nur der erste Besucher diesen Befehl abarbeiten kann. Schließlich werden doch mysql-Befehle einer nach dem anderen abgearbeitet?!
Das mit dem UPDATE != verstehe ich nicht. Das betrifft doch automatisch alle anderen Zeilen, die in der Tabelle sind und nicht auf "neuer Wert" lauten. Demnach könnte ich doch nur ein Gewinnspiel pro Tabelle machen. Richtig?
meine Foren: http://www.maxrev.de/communities.htm
Ich kaufe Dein Forum! Angebote bitte an marc at gutt punkt it
Ich kaufe Dein Forum! Angebote bitte an marc at gutt punkt it
- gn#36
- Ehrenadmin
- Beiträge: 9313
- Registriert: 01.10.2006 16:20
- Wohnort: Ganz in der Nähe...
- Kontaktdaten:
Ich weiß nicht ob das wirklich besser wäre, aber ich würde diesen Befehl ein wenig anders durchführen: Wenn dann mehr als eine Zeile bearbeitet wird dann ist irgendwas schief gegangen (zumindest wenn das als affected Row gezählt wird).
Code: Alles auswählen
UPDATE tabelle SET feld='neuer Wert' WHERE id = 11 OR feld = 'neuer Wert'
Begegnungen mit dem Chaos sind fast unvermeidlich, Aber nicht katastrophal, solange man den Durchblick behält.
Übertreiben sollte man's im Forum aber nicht mit dem Chaos, denn da sollen ja andere durchblicken und nicht nur man selbst.
Übertreiben sollte man's im Forum aber nicht mit dem Chaos, denn da sollen ja andere durchblicken und nicht nur man selbst.
Das Problem ist, dass "neuer Wert" ja wahrscheinlich die user_id enthält, die dann im Fehlerfall durch Dein UPDATE überschrieben wird.
Dann kann man ja nicht mehr ermitteln, wer der erste war, der das UPDATE gemacht hatte.
Was denkst Du denn zu meiner Frage? Also ob ein DELETE doppelt ausgeführt werden kann?
Wenn das nämlich nicht passieren kann, dann kann man im Erfolgsfall ja einen zusätzlich INSERT in einer Gewinnertabelle setzen. In dem Moment löscht man sozusagen das betreffende Gewinnspiel (wenn das nur einmal möglich ist, auch wenn 1000 Leute mehr oder weniger gleichzeitig die Seite aufrufen) und der, der es zuerst löschen konnte, der erreicht per switch = true eine neue INSERT user_id GEWINNERTABELLE.
Ich hoffe man konnte mir folgen. Sonst mache ich einfach mal ein Codebeispiel.
Gruß
EDIT:
Doch mit Deiner Methode gehts auch. Ebenfalls mit dem Switch. D.h. wenn nur eine affected_rows dann setzt man das "INSERT user_id GEWINNERTABELLE". Alle anderen kommen um den switch affected_rows = 1 nicht rum. Wäre also auch denkbar.
Dann kann man ja nicht mehr ermitteln, wer der erste war, der das UPDATE gemacht hatte.
Was denkst Du denn zu meiner Frage? Also ob ein DELETE doppelt ausgeführt werden kann?
Wenn das nämlich nicht passieren kann, dann kann man im Erfolgsfall ja einen zusätzlich INSERT in einer Gewinnertabelle setzen. In dem Moment löscht man sozusagen das betreffende Gewinnspiel (wenn das nur einmal möglich ist, auch wenn 1000 Leute mehr oder weniger gleichzeitig die Seite aufrufen) und der, der es zuerst löschen konnte, der erreicht per switch = true eine neue INSERT user_id GEWINNERTABELLE.
Ich hoffe man konnte mir folgen. Sonst mache ich einfach mal ein Codebeispiel.
Gruß
EDIT:
Doch mit Deiner Methode gehts auch. Ebenfalls mit dem Switch. D.h. wenn nur eine affected_rows dann setzt man das "INSERT user_id GEWINNERTABELLE". Alle anderen kommen um den switch affected_rows = 1 nicht rum. Wäre also auch denkbar.
meine Foren: http://www.maxrev.de/communities.htm
Ich kaufe Dein Forum! Angebote bitte an marc at gutt punkt it
Ich kaufe Dein Forum! Angebote bitte an marc at gutt punkt it
Re: doppelte SQL-Abfrage 100%-tig verhindern - Gewinnspiel
Warum überhaupt update? Warum nicht einfach ein select und ein insert? Wenn mehr als ein Eintag drin steht war der erste Eintrag auch der erste.
Und kann überhaupt ein zweites select erfolgen, wenn der Prozeß vom ersten noch läuft, sprich sein select mit anschließendem update?
Und kann überhaupt ein zweites select erfolgen, wenn der Prozeß vom ersten noch läuft, sprich sein select mit anschließendem update?
Ein normaler Prozessor macht nicht zwei Sachen gleichzeitig. Deshalb frage ich mich so wie so, was das soll. Wenn die Datenbank also nur von einem Server angesprochen wird und der Prozessor Prozesse nicht stückweise abwechselt abarbeitet, sollte selbst die erste Lösung sicher sein.mgutt hat geschrieben:Ist es da trotzdem möglich, dass ein 100% synchroner Aufruf mehrere TRUE zurückgibt oder kann ich das 100%-tig ausschließen?
- gn#36
- Ehrenadmin
- Beiträge: 9313
- Registriert: 01.10.2006 16:20
- Wohnort: Ganz in der Nähe...
- Kontaktdaten:
Warum sollte ein Server nicht mit mehreren Prozessen gleichzeitig auf eine Datenbank zugreifen? Genau das ist doch die Idee bei Mehrkernprozessoren, die Parallelität. Und selbst wenn das nicht 100% pararallel laufen kann so wird es doch so parallel laufen wie es nur geht, Prozesse werden abwechselnd unterbrochen und wieder fortgesetzt. Jedes moderne Betriebssystem basiert darauf, ich glaube beim Webserver Apache lässt sich die Anzahl der Kindsprozesse sogar händisch festlegen.
Aber um zum Thema zurückzukommen:
Man könnte auch eine Tabelle mit einer einzigen Spalte anlegen und diese als Primärschlüssel definieren. Wenn man dann einen einmaligen Vorgang hat, so muss man nur einen Insert Befehl mit festgelegtem Inhalt durchführen. Sofern dieser Inhalt noch nicht drin war ist alles in Ordnung und man kann alle übrigen Dinge durchführen. War der Eintrag schon vorhanden gibt es einen Fehler (bzw. mysql wird false zurückliefern) und wir wissen, dass dieser Eintrag schon stattgefunden hat und können abbrechen. Da das nur ein SQL Befehl ist, sind Inkonsistenzen ausgeschlossen.
Aber um zum Thema zurückzukommen:
Man könnte auch eine Tabelle mit einer einzigen Spalte anlegen und diese als Primärschlüssel definieren. Wenn man dann einen einmaligen Vorgang hat, so muss man nur einen Insert Befehl mit festgelegtem Inhalt durchführen. Sofern dieser Inhalt noch nicht drin war ist alles in Ordnung und man kann alle übrigen Dinge durchführen. War der Eintrag schon vorhanden gibt es einen Fehler (bzw. mysql wird false zurückliefern) und wir wissen, dass dieser Eintrag schon stattgefunden hat und können abbrechen. Da das nur ein SQL Befehl ist, sind Inkonsistenzen ausgeschlossen.
Begegnungen mit dem Chaos sind fast unvermeidlich, Aber nicht katastrophal, solange man den Durchblick behält.
Übertreiben sollte man's im Forum aber nicht mit dem Chaos, denn da sollen ja andere durchblicken und nicht nur man selbst.
Übertreiben sollte man's im Forum aber nicht mit dem Chaos, denn da sollen ja andere durchblicken und nicht nur man selbst.
- Emanuelle_1982
- Mitglied
- Beiträge: 535
- Registriert: 06.03.2006 18:37
- Wohnort: Nümbrecht & Siegen
- Kontaktdaten:
eine Möglichkeit ohne Transaktionen und modernen Schnickschnack (auf den man leider bei free oder billigen Hostern verzichten muss) wäre:
Tabelle Gewinnspiel
Tabelle sollte (nicht wurde!!) gezeigt werden
(Werte: autoincrement index, userID, (Gewinnspiel ID falls es mehrere sind))
was meine ich nun damit?
sobald ein user die seite erreicht trägt er sich in die Tabelle ein, danach wird überprüft ob seine UserID den kleinsten autoincrement Index Wert hat, wenn ja gewonnen, wenn nein war jemand anderes schneller
nun kann das Gewinnspiel - wenn nötig - noch mal zu rate gezogen werden, sowie gelöscht werden
danach löscht man - falls mehrere Gewinnspiele laufen - alle Einträge mit der entsprechenden Gewinnspiel ID aus der 2. Tabelle
die Reihenfolge sollte so eingehalten werden. Würde man zum Beispiel die letzten Beiträge verdrehen könnte jemand durch zufall noch mal erster bei einem Gewinnspiel werden, zu einem Gewinnspiel was vermutlich genau in dem Moment gelöscht wird
Naja... es ist nicht allzu schonend bei der Hardware, aber auch kein Hardware Fresser...
und da du sicher nicht 1000 Gewinne in der gleichen Sekunde gewinnst wird man davon nicht viel, wenn überhaupt, was merken
Liebe Grüße
Emma
Tabelle Gewinnspiel
Tabelle sollte (nicht wurde!!) gezeigt werden
(Werte: autoincrement index, userID, (Gewinnspiel ID falls es mehrere sind))
was meine ich nun damit?
sobald ein user die seite erreicht trägt er sich in die Tabelle ein, danach wird überprüft ob seine UserID den kleinsten autoincrement Index Wert hat, wenn ja gewonnen, wenn nein war jemand anderes schneller
nun kann das Gewinnspiel - wenn nötig - noch mal zu rate gezogen werden, sowie gelöscht werden
danach löscht man - falls mehrere Gewinnspiele laufen - alle Einträge mit der entsprechenden Gewinnspiel ID aus der 2. Tabelle
die Reihenfolge sollte so eingehalten werden. Würde man zum Beispiel die letzten Beiträge verdrehen könnte jemand durch zufall noch mal erster bei einem Gewinnspiel werden, zu einem Gewinnspiel was vermutlich genau in dem Moment gelöscht wird
Naja... es ist nicht allzu schonend bei der Hardware, aber auch kein Hardware Fresser...
und da du sicher nicht 1000 Gewinne in der gleichen Sekunde gewinnst wird man davon nicht viel, wenn überhaupt, was merken
Liebe Grüße
Emma
Ihr vergesst, dass es passieren kann (in der Millesekunde), dass nahezu synchron mehrere User den ersten SELECT ausführen können, bevor beim allerersten User der INSERT fertig ist.
D.h. es wird schon nach und nach abgearbeitet, aber in der Zeit zwischen den beiden Abfragen, kann es durchaus sein, dass noch ein SELECT eines anderen durchhuscht.
Ich hatte das bereits mehrmals in einem extrem frequenten Thema. Dort "beschwerten" sich User, dass ihr Beitrag erst auf Seite X und dann auf Seite Y angezeigt wurde. Weil dort ein weiterer User parallel geschrieben hat (INSERT) und dieser aber noch nicht vom SELECT erfasst werden konnte.
D.h. es wird schon nach und nach abgearbeitet, aber in der Zeit zwischen den beiden Abfragen, kann es durchaus sein, dass noch ein SELECT eines anderen durchhuscht.
Ich hatte das bereits mehrmals in einem extrem frequenten Thema. Dort "beschwerten" sich User, dass ihr Beitrag erst auf Seite X und dann auf Seite Y angezeigt wurde. Weil dort ein weiterer User parallel geschrieben hat (INSERT) und dieser aber noch nicht vom SELECT erfasst werden konnte.
meine Foren: http://www.maxrev.de/communities.htm
Ich kaufe Dein Forum! Angebote bitte an marc at gutt punkt it
Ich kaufe Dein Forum! Angebote bitte an marc at gutt punkt it