MySQL: Transaktionen gegen Sperrtabellen

110

Ich bin ein bisschen verwirrt mit Transaktionen im Vergleich zum Sperren von Tabellen, um die Datenbankintegrität sicherzustellen und sicherzustellen, dass SELECT und UPDATE synchron bleiben und keine andere Verbindung diese stört. Ich muss einfach:

SELECT * FROM table WHERE (...) LIMIT 1

if (condition passes) {
   // Update row I got from the select 
   UPDATE table SET column = "value" WHERE (...)

   ... other logic (including INSERT some data) ...
}

Ich muss sicherstellen, dass keine anderen Abfragen stören und dasselbe ausführen SELECT(Lesen des 'alten Werts', bevor diese Verbindung die Aktualisierung der Zeile beendet.

Ich weiß, dass ich standardmäßig LOCK TABLES tablenur sicherstellen kann, dass jeweils nur eine Verbindung dies tut, und sie entsperren kann, wenn ich fertig bin, aber das scheint übertrieben. Würde das Umschließen in eine Transaktion dasselbe bewirken (sicherstellen, dass keine andere Verbindung den gleichen Prozess versucht, während eine andere noch verarbeitet wird)? Oder wäre ein SELECT ... FOR UPDATEoder SELECT ... LOCK IN SHARE MODEbesser?

Ryan
quelle

Antworten:

173

Durch das Sperren von Tabellen wird verhindert, dass andere DB-Benutzer die von Ihnen gesperrten Zeilen / Tabellen beeinflussen. Sperren an und für sich stellen jedoch NICHT sicher, dass Ihre Logik in einem konsistenten Zustand ausgegeben wird.

Denken Sie an ein Bankensystem. Wenn Sie eine Rechnung online bezahlen, sind mindestens zwei Konten von der Transaktion betroffen: Ihr Konto, von dem das Geld abgezogen wird. Und das Konto des Empfängers, auf das das Geld überwiesen wird. Und das Konto der Bank, auf das sie gerne alle für die Transaktion berechneten Servicegebühren einzahlen. Angesichts der Tatsache, dass Banken (wie heutzutage jeder weiß) außerordentlich dumm sind, nehmen wir an, dass ihr System folgendermaßen funktioniert:

$balance = "GET BALANCE FROM your ACCOUNT";
if ($balance < $amount_being_paid) {
    charge_huge_overdraft_fees();
}
$balance = $balance - $amount_being paid;
UPDATE your ACCOUNT SET BALANCE = $balance;

$balance = "GET BALANCE FROM receiver ACCOUNT"
charge_insane_transaction_fee();
$balance = $balance + $amount_being_paid
UPDATE receiver ACCOUNT SET BALANCE = $balance

Jetzt, ohne Sperren und ohne Transaktionen, ist dieses System anfällig für verschiedene Rennbedingungen, von denen die größte darin besteht, dass mehrere Zahlungen auf Ihrem Konto oder auf dem Konto des Empfängers parallel ausgeführt werden. Während Ihr Code Ihr Guthaben abgerufen hat und die riesigen_overdraft_fees () und so weiter ausführt, ist es durchaus möglich, dass bei einer anderen Zahlung dieselbe Art von Code parallel ausgeführt wird. Sie werden Ihr Guthaben abrufen (z. B. 100 US-Dollar), ihre Transaktionen durchführen (die 20 US-Dollar, die Sie bezahlen, und die 30 US-Dollar, mit denen sie Sie verarschen, herausnehmen), und jetzt haben beide Codepfade zwei unterschiedliche Guthaben: 80 US-Dollar und $ 70. Abhängig davon, welches zuletzt endet, erhalten Sie eines dieser beiden Guthaben auf Ihrem Konto, anstatt der 50 US-Dollar, die Sie hätten erhalten sollen (100 bis 20 bis 30 US-Dollar). In diesem Fall "Bankfehler zu Ihren Gunsten"

Angenommen, Sie verwenden Sperren. Ihre Rechnungszahlung (20 US-Dollar) geht zuerst auf die Pipe, sodass Sie Ihren Kontodatensatz gewinnen und sperren. Jetzt haben Sie die ausschließliche Verwendung und können die 20 USD vom Guthaben abziehen und das neue Guthaben in Ruhe zurückschreiben ... und Ihr Konto erhält erwartungsgemäß 80 USD. Aber ... äh ... Sie versuchen, das Konto des Empfängers zu aktualisieren, und es ist gesperrt und länger gesperrt, als es der Code zulässt, wodurch Ihre Transaktion zeitlich begrenzt wird ... Wir haben es mit dummen Banken zu tun, anstatt einen richtigen Fehler zu haben Handling, der Code zieht nur ein exit(), und Ihre 20 Dollar verschwinden in einem Elektronenstoß. Jetzt haben Sie 20 US-Dollar verloren, und Sie schulden dem Empfänger immer noch 20 US-Dollar, und Ihr Telefon wird wieder in Besitz genommen.

Also ... Transaktionen eingeben. Sie starten eine Transaktion, Sie belasten Ihr Konto mit 20 USD, Sie versuchen, dem Empfänger 20 USD gutzuschreiben ... und etwas explodiert erneut. Aber diesmal kann exit()der Code nicht mehr rollback, und Ihre 20 US-Dollar werden Ihrem Konto auf magische Weise wieder gutgeschrieben.

Am Ende läuft es darauf hinaus:

Sperren verhindern, dass andere Benutzer Datenbankeinträge stören, mit denen Sie sich befassen. Transaktionen verhindern, dass "spätere" Fehler "frühere" Dinge stören, die Sie getan haben. Keiner von beiden allein kann garantieren, dass die Dinge am Ende gut laufen. Aber zusammen tun sie es.

in der morgigen Lektion: Die Freude an Deadlocks.

Marc B.
quelle
4
Ich bin auch / immer noch verwirrt. Angenommen, das Empfängerkonto hatte zu Beginn 100 US-Dollar und wir fügen die 20-Dollar-Rechnungszahlung von unserem Konto hinzu. Mein Verständnis von Transaktionen ist, dass bei jedem Start jeder Transaktion die Datenbank in dem Zustand angezeigt wird, in dem sie sich zu Beginn der Transaktion befand. dh: bis wir es ändern, hat das Empfängerkonto 100 $. Also ... wenn wir 20 Dollar hinzufügen, setzen wir tatsächlich einen Saldo von 120 Dollar. Aber was passiert, wenn während unserer Transaktion jemand das Empfängerkonto auf 0 US-Dollar reduziert hat? Wird das irgendwie verhindert? Erhalten sie auf magische Weise wieder 120 Dollar? Werden deshalb auch Schlösser benötigt?
Russ
Ja, hier kommen Schlösser ins Spiel. Ein ordnungsgemäßes System würde den Datensatz schriftlich sperren, sodass niemand sonst den Datensatz aktualisieren kann, während die Transaktion fortschreitet. Ein paranoides System würde die Aufzeichnung bedingungslos sperren, so dass auch niemand die "veraltete" Bilanz lesen könnte.
Marc B
1
Betrachten Sie Transaktionen grundsätzlich als Sicherung von Dingen in Ihrem Codepfad. Sperren sichern Dinge über "parallele" Codepfade. Bis Deadlocks schlagen ...
Marc B
1
@MarcB, warum müssen wir also explizit sperren, wenn die alleinige Verwendung von Transaktionen bereits garantiert, dass die Sperren vorhanden sind? Wird es überhaupt einen Fall geben, in dem wir explizit sperren müssen, weil Transaktionen allein nicht ausreichen?
Pacerier
2
Diese Antwort ist nicht korrekt und kann zu falschen Schlussfolgerungen führen. Diese Aussage: "Sperren verhindern, dass andere Personen Datenbankeinträge stören, mit denen Sie sich befassen. Transaktionen verhindern, dass" spätere "Fehler" frühere "Dinge stören, die Sie getan haben Ende. Aber zusammen tun sie es. " - würde dich feuern lassen, es ist extrem falsch und dumm Siehe Artikel: en.wikipedia.org/wiki/ACID , en.wikipedia.org/wiki/Isolation_(database_systems) und dev.mysql.com/doc/refman/5.1/ de /…
Nikola Svitlica
14

Sie möchten eine SELECT ... FOR UPDATEoder SELECT ... LOCK IN SHARE MODEinnerhalb einer Transaktion, wie Sie sagten, da normalerweise SELECTs, unabhängig davon, ob sie sich in einer Transaktion befinden oder nicht, eine Tabelle nicht sperren. Welche Sie auswählen, hängt davon ab, ob andere Transaktionen diese Zeile lesen können, während Ihre Transaktion ausgeführt wird.

http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html

START TRANSACTION WITH CONSISTENT SNAPSHOTwird den Trick nicht für Sie tun, da andere Transaktionen noch kommen und diese Zeile ändern können. Dies wird ganz oben auf dem Link unten erwähnt.

Wenn andere Sitzungen gleichzeitig dieselbe Tabelle [...] aktualisieren, wird die Tabelle möglicherweise in einem Zustand angezeigt, der in der Datenbank nie vorhanden war.

http://dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html

Alison R.
quelle
7

Transaktionskonzepte und Sperren sind unterschiedlich. Die Transaktion verwendete jedoch Sperren, um die ACID-Prinzipien zu befolgen. Wenn Sie möchten, dass die Tabelle verhindert, dass andere zum gleichen Zeitpunkt lesen / schreiben, während Sie lesen / schreiben, benötigen Sie dazu eine Sperre. Wenn Sie die Datenintegrität und -konsistenz sicherstellen möchten, sollten Sie Transaktionen besser verwenden. Ich denke, gemischte Konzepte von Isolationsstufen bei Transaktionen mit Sperren. Bitte suchen Sie nach Isolationsstufen von Transaktionen. SERIALIZE sollte die gewünschte Stufe sein.

tczhaodachuan
quelle
Dies sollte die richtige Antwort sein. Durch das Sperren werden Rennbedingungen verhindert, und durch Transaktionen werden mehrere Tabellen mit abhängigen Daten aktualisiert. Zwei völlig unterschiedliche Konzepte, obwohl Transaktionen Sperren verwenden.
Blaues Wasser
6

Ich hatte ein ähnliches Problem beim Versuch eines IF NOT EXISTS ...und dann beim Ausführen eines, INSERTdas eine Race-Bedingung verursachte, wenn mehrere Threads dieselbe Tabelle aktualisierten.

Ich habe hier die Lösung für das Problem gefunden: So schreiben Sie INSERT IF NOT EXISTS-Abfragen in Standard-SQL

Mir ist klar, dass dies Ihre Frage nicht direkt beantwortet, aber das gleiche Prinzip, eine Prüfung und Einfügung als einzelne Aussage durchzuführen, ist sehr nützlich. Sie sollten es ändern können, um Ihr Update durchzuführen.

Tony
quelle
2

Sie sind mit Lock & Transaction verwechselt. Das sind zwei verschiedene Dinge in RMDB. Die Sperre verhindert gleichzeitige Vorgänge, während sich die Transaktion auf die Datenisolierung konzentriert. In diesem großartigen Artikel finden Sie Erläuterungen und eine elegante Lösung.

David
quelle
1
Sperren verhindern, dass andere Benutzer in Datensätze eingreifen, mit denen Sie arbeiten, und beschreiben kurz und bündig, was sie tun. Transaktionen verhindern, dass spätere Fehler (die von anderen, die parallel Änderungen vornehmen) frühere Dinge stören, die Sie getan haben (indem sie ein Rollback zulassen, falls jemand etwas getan hat parallel) fasst Transaktionen ziemlich gut zusammen ... was ist verwirrt über sein Verständnis dieser Themen?
Steviesama
1

Ich würde eine verwenden

START TRANSACTION WITH CONSISTENT SNAPSHOT;

zu Beginn und a

COMMIT;

zum Schluss mit.

Alles, was Sie dazwischen tun, ist von den anderen Benutzern Ihrer Datenbank isoliert, wenn Ihre Speicher-Engine Transaktionen unterstützt (InnoDB).

Martin Schapendonk
quelle
1
Außer, dass die Tabelle, aus der er auswählt, nicht für andere Sitzungen gesperrt wird, es sei denn, er sperrt sie ausdrücklich (oder bis sein UPDATE stattfindet), was bedeutet, dass andere Sitzungen hinzukommen und sie zwischen SELECT und UPDATE ändern können.
Alison R.
Nachdem ich in der MySQL-Dokumentation über START TRANSACTION WITH CONSISTENT SNAPSHOT nachgelesen habe, sehe ich nicht, wo eine andere Verbindung tatsächlich daran gehindert wird, dieselbe Zeile zu aktualisieren. Nach meinem Verständnis würde jedoch die Tabelle zu Beginn der Transaktion gestartet. Wenn also eine andere Transaktion ausgeführt wird, bereits eine Zeile erhalten hat und kurz vor der Aktualisierung steht, wird die Zeile bei der zweiten Transaktion noch angezeigt, bevor sie aktualisiert wurde. Es könnte daher möglicherweise versuchen, dieselbe Zeile zu aktualisieren, in der sich die andere Transaktion befindet. Ist das richtig oder fehlt mir etwas im Fortschritt?
Ryan
1
@ Ryan Es macht keine Sperren; Du hast Recht. Das Sperren (oder nicht) wird durch die Art der von Ihnen ausgeführten Vorgänge (SELECT / UPDATE / DELETE) bestimmt.
Alison R.
4
Aha. Es gibt Ihrer eigenen Transaktion Lesekonsistenz, hindert jedoch andere Benutzer nicht daran, eine Zeile unmittelbar vor Ihnen zu ändern.
Martin Schapendonk