Festlegen eines Zeitlimits für eine Transaktion in MySQL / InnoDB


11

Dies ergab sich aus dieser verwandten Frage , bei der ich wissen wollte, wie zwei Transaktionen in einem trivialen Fall (bei dem beide nur in einer einzigen Zeile ausgeführt werden) nacheinander ausgeführt werden können. Ich habe eine Antwort erhalten - SELECT ... FOR UPDATEals erste Zeile beider Transaktionen verwenden -, aber dies führt zu einem Problem: Wenn die erste Transaktion nie festgeschrieben oder zurückgesetzt wird, wird die zweite Transaktion auf unbestimmte Zeit blockiert. Die innodb_lock_wait_timeoutVariable legt die Anzahl der Sekunden fest, nach denen dem Client, der versucht, die zweite Transaktion durchzuführen, die Meldung "Entschuldigung, versuchen Sie es erneut" mitgeteilt wird. Soweit ich das beurteilen kann, wird er es bis zum nächsten Neustart des Servers erneut versuchen. So:

  1. Sicherlich muss es eine Möglichkeit geben, eine zu erzwingen, ROLLBACKwenn eine Transaktion ewig dauert? Muss ich einen Daemon verwenden, um solche Transaktionen zu beenden, und wenn ja, wie würde ein solcher Daemon aussehen?
  2. Wenn eine Verbindung durch wait_timeoutoder interactive_timeoutwährend der Transaktion unterbrochen wird, wird die Transaktion zurückgesetzt? Gibt es eine Möglichkeit, dies von der Konsole aus zu testen?

Erläuterung : innodb_lock_wait_timeoutLegt die Anzahl der Sekunden fest, die eine Transaktion auf die Freigabe einer Sperre wartet, bevor sie aufgibt. Was ich möchte, ist eine Möglichkeit , die Freigabe eines Schlosses zu erzwingen.

Update 1 : Hier ist ein einfaches Beispiel, das zeigt, warum innodb_lock_wait_timeoutnicht ausreicht, um sicherzustellen, dass die zweite Transaktion nicht von der ersten blockiert wird:

START TRANSACTION;
SELECT SLEEP(55);
COMMIT;

Mit der Standardeinstellung von innodb_lock_wait_timeout = 50wird diese Transaktion nach 55 Sekunden fehlerfrei abgeschlossen. Wenn Sie UPDATEvor der SLEEPZeile eine hinzufügen und dann eine zweite Transaktion von einem anderen Client initiieren, der versucht, SELECT ... FOR UPDATEdieselbe Zeile zu erstellen, tritt die zweite Transaktion ab, nicht diejenige, die eingeschlafen ist.

Was ich suche, ist eine Möglichkeit, dem erholsamen Schlaf dieser Transaktion ein Ende zu setzen.

Update 2 : Als Reaktion auf die Bedenken von hobodave, wie realistisch das obige Beispiel ist, gibt es hier ein alternatives Szenario: Ein DBA stellt eine Verbindung zu einem Live-Server her und wird ausgeführt

START TRANSACTION
SELECT ... FOR UPDATE

Dabei sperrt die zweite Zeile eine Zeile, in die die Anwendung häufig schreibt. Dann wird der DBA unterbrochen und geht weg, wobei er vergisst, die Transaktion zu beenden. Die Anwendung wird angehalten, bis die Zeile entsperrt ist. Ich möchte die Zeit minimieren, in der die Anwendung aufgrund dieses Fehlers hängen bleibt.


Sie haben gerade Ihre Frage so aktualisiert, dass sie vollständig mit Ihrer ursprünglichen Frage in Konflikt steht. Sie geben in Fettdruck an: "Wenn die erste Transaktion niemals festgeschrieben oder zurückgesetzt wird, wird die zweite Transaktion auf unbestimmte Zeit blockiert." Sie widerlegen dies mit Ihrem neuesten Update: "Es ist die zweite Transaktion, die abläuft, nicht die, die eingeschlafen ist." -- Ernsthaft?!
Hobodave

Ich nehme an, meine Formulierung hätte klarer sein können. Mit "auf unbestimmte Zeit blockiert" meinte ich, dass die zweite Transaktion mit einer Fehlermeldung mit der Meldung "Versuch, die Transaktion neu zu starten" abläuft. aber dies wäre fruchtlos, weil es nur wieder eine Auszeit geben würde. Die zweite Transaktion wird also blockiert - sie wird niemals abgeschlossen, da das Zeitlimit weiterhin überschritten wird.
Trevor Burnham

@ Trevor: Deine Formulierung war vollkommen klar. Sie haben Ihre Frage einfach aktualisiert, um eine ganz andere Sache zu stellen, was eine extrem schlechte Form ist.
Hobodave

Die Frage war immer dieselbe: Es ist ein Problem, dass die zweite Transaktion auf unbestimmte Zeit blockiert wird, wenn die erste Transaktion niemals festgeschrieben oder zurückgesetzt wird. Ich möchte ROLLBACKdie erste Transaktion erzwingen , wenn der Abschluss länger als nSekunden dauert . Gibt es eine Möglichkeit dazu?
Trevor Burnham

1
@ TrevorBurnham Ich frage mich auch, warum MYSQLes keine Konfiguration gibt, um dieses Szenario zu verhindern. Weil es aufgrund der Verantwortungslosigkeit des Clients nicht akzeptabel ist, dass der Server hängen bleibt. Ich fand keine Schwierigkeit, Ihre Frage zu verstehen, auch sie ist so relevant.
Dinoop Paloli

Antworten:



3

Da Ihre Frage hier in ServerFault gestellt wird, ist es logisch anzunehmen, dass Sie nach einer MySQL-Lösung für ein MySQL-Problem suchen, insbesondere in dem Bereich, in dem ein Systemadministrator und / oder ein DBA über Fachkenntnisse verfügen Abschnitt behandelt Ihre Fragen:

Wenn die erste Transaktion niemals festgeschrieben oder zurückgesetzt wird, wird die zweite Transaktion auf unbestimmte Zeit blockiert

Nein, das wird es nicht. Ich denke du verstehst nicht innodb_lock_wait_timeout. Es macht genau das, was Sie brauchen.

Es wird mit einem Fehler zurückgegeben, wie im Handbuch angegeben:

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
  • Per Definition ist dies nicht unbestimmt . Wenn Ihre Anwendung erneut eine Verbindung herstellt und wiederholt blockiert, blockiert Ihre Anwendung "auf unbestimmte Zeit", nicht die Transaktion. Die zweite Transaktion blockiert definitiv für innodb_lock_wait_timeoutSekunden.

Standardmäßig wird die Transaktion nicht zurückgesetzt. Es liegt in der Verantwortung Ihres Anwendungscodes, zu entscheiden, wie dieser Fehler behandelt werden soll, ob dies erneut versucht oder ein Rollback durchgeführt wird.

Wenn Sie ein automatisches Rollback wünschen, wird dies auch im Handbuch erläutert:

Die aktuelle Transaktion wird nicht zurückgesetzt. (Damit die gesamte Transaktion zurückgesetzt wird, starten Sie den Server mit der --innodb_rollback_on_timeoutOption.


RE: Ihre zahlreichen Updates und Kommentare

Zunächst haben Sie in Ihren Kommentaren angegeben, dass Sie "beabsichtigt" haben, zu sagen, dass Sie eine Möglichkeit zum Timeout der ersten Transaktion wünschen, die auf unbestimmte Zeit blockiert wird. Dies geht aus Ihrer ursprünglichen Frage nicht hervor und steht im Widerspruch zu "Wenn die erste Transaktion niemals festgeschrieben oder zurückgesetzt wird, wird die zweite Transaktion auf unbestimmte Zeit blockiert".

Trotzdem kann ich diese Frage auch beantworten. Das MySQL-Protokoll hat kein "Abfrage-Timeout". Dies bedeutet, dass Sie das Zeitlimit für die erste blockierte Transaktion nicht überschreiten können. Sie müssen warten, bis es beendet ist, oder die Sitzung beenden. Wenn die Sitzung beendet wird, setzt der Server die Transaktion automatisch zurück.

Die einzige andere Alternative wäre, eine MySQL-Bibliothek zu verwenden oder zu schreiben, die nicht blockierende E / A verwendet, wodurch Ihre Anwendung den Thread / Fork, der die Abfrage durchführt, nach N Sekunden beenden kann. Die Implementierung und Verwendung einer solchen Bibliothek geht über den Rahmen von ServerFault hinaus. Dies ist eine geeignete Frage für StackOverflow .

Zweitens haben Sie in Ihren Kommentaren Folgendes angegeben:

In meiner Frage ging es mir eigentlich mehr um ein Szenario, in dem die Client-App im Verlauf einer Transaktion hängt (z. B. in einer Endlosschleife gefangen wird), als um ein Szenario, in dem die Transaktion am Ende von MySQL lange dauert.

Dies war in Ihrer ursprünglichen Frage überhaupt nicht ersichtlich und ist es immer noch nicht. Dies konnte erst erkannt werden, nachdem Sie diesen ziemlich wichtigen Leckerbissen im Kommentar geteilt hatten.

Wenn dies tatsächlich das Problem ist, das Sie lösen möchten, haben Sie es leider im falschen Forum gefragt. Sie haben ein Programmierproblem auf Anwendungsebene beschrieben, für das eine Programmierlösung erforderlich ist, die MySQL nicht bereitstellen kann und die außerhalb des Bereichs dieser Community liegt. Ihre letzte Antwort beantwortet die Frage "Wie verhindere ich, dass ein Ruby-Programm eine Endlosschleife durchläuft?". Diese Frage ist für diese Community nicht thematisch und sollte auf StackOverflow gestellt werden .


Entschuldigung, aber während es wahr ist, wenn die Transaktion auf eine Sperre wartet (wie der Name und die Dokumentation von innodb_lock_wait_timeoutvorschlagen), ist dies in der von mir gestellten Situation nicht der Fall: Wo der Client hängt oder abstürzt, bevor er die Änderung erhält, ein COMMIToder auszuführen ROLLBACK.
Trevor Burnham

@ Trevor: Du liegst einfach falsch. Dies ist trivial zu testen. Ich habe es gerade an meinem Ende getestet. Bitte nehmen Sie Ihre Ablehnung zurück.
Hobodave

@hobo, nur weil dein Recht nicht bedeutet, dass er es mögen muss.
Chris S

Chris, kannst du erklären, wie er Recht hat? Wie erfolgt die zweite Transaktion, wenn keine COMMIToder eine ROLLBACKNachricht gesendet wird, um die erste Transaktion aufzulösen? Hobodave, vielleicht wäre es hilfreich, wenn Sie Ihren Testcode vorlegen würden.
Trevor Burnham

2
@ Trevor: Du machst kein bisschen Sinn. Sie möchten Transaktionen serialisieren, oder? Ja, das haben Sie in Ihrer anderen Frage gesagt und wiederholen Sie dies hier in dieser Frage. Wie um alles in der Welt würden Sie erwarten, dass die zweite Transaktion abgeschlossen wird, wenn die erste Transaktion noch nicht abgeschlossen ist? Sie haben ausdrücklich angewiesen, darauf zu warten, dass die Sperre für diese Zeile freigegeben wird. Deshalb muss es warten. Was verstehst du davon nicht?
Hobodave

1

In einer ähnlichen Situation entschied sich mein Team für pt-kill ( https://www.percona.com/doc/percona-toolkit/2.1/pt-kill.html ). Es kann Abfragen melden oder beenden, die (unter anderem) zu lange ausgeführt werden. Es ist dann möglich, es alle X Minuten in einem Cron auszuführen.

Das Problem bestand darin, dass eine Abfrage mit einer unerwartet langen (z. B. unbestimmten) Ausführungszeit eine Sicherungsprozedur sperrte, die versuchte, Tabellen mit Lesesperre zu leeren, wodurch wiederum alle anderen Abfragen zum "Warten auf das Löschen von Tabellen" gesperrt wurden, die dann nie abgelaufen sind weil sie nicht auf eine Sperre warten, was zu Fehlern in der Anwendung führte, was letztendlich dazu führte, dass keine Warnungen an Administratoren und Anwendungen angehalten wurden.

Das Update bestand offensichtlich darin, die anfängliche Abfrage zu korrigieren. Um jedoch zu vermeiden, dass in Zukunft versehentlich etwas passiert, wäre es großartig, wenn Transaktionen / Abfragen, die länger als eine bestimmte Zeit dauern, automatisch beendet (oder gemeldet) werden könnten. Welcher Autor der Frage scheint zu fragen. Dies ist mit pt-kill möglich.


0

Eine einfache Lösung besteht darin, eine gespeicherte Prozedur zu haben, um die Abfragen zu beenden, die länger dauern als die erforderliche timeoutZeit, und die innodb_rollback_on_timeoutOption zusammen mit dieser zu verwenden.


-2

Nach einer Weile googeln sieht es so aus, als gäbe es keinen direkten Möglichkeit zu geben, ein Zeitlimit pro Transaktion . Hier ist die beste Lösung, die ich finden konnte:

Der Befehl

SHOW PROCESSLIST;

gibt Ihnen eine Tabelle mit allen aktuell ausgeführten Befehlen und der Zeit (in Sekunden) seit ihrem Start. Wenn ein Client keine Befehle mehr erteilt, wird er als SleepBefehl bezeichnet, wobei die TimeSpalte die Anzahl der Sekunden seit dem Abschluss seines letzten Befehls angibt. Sie können also manuell darauf zugreifen und KILLalles, was einen TimeWert von mehr als 5 Sekunden hat, wenn Sie sicher sind, dass keine Abfrage länger als 5 Sekunden in Ihrer Datenbank dauern sollte. Wenn der Prozess mit der ID 3 beispielsweise den Wert 12 in seiner TimeSpalte hat, können Sie dies tun

KILL 3;

Die Dokumentation für die KILL-Syntax schlägt vor, dass eine Transaktion, die vom Thread mit dieser ID ausgeführt wird, zurückgesetzt wird (obwohl ich dies nicht getestet habe, seien Sie also vorsichtig).

Aber wie kann man dies automatisieren und alle 5 Sekunden alle Überstundenskripte beenden? Der erste Kommentar auf derselben Seite gibt uns einen Hinweis, aber der Code ist in PHP; Es scheint, dass wir im MySQL-Client kein SELECTOn durchführen können SHOW PROCESSLIST. Angenommen, wir haben einen PHP-Daemon, den wir alle $MAX_TIMESekunden ausführen , könnte er ungefähr so ​​aussehen:

$result = mysql_query("SHOW FULL PROCESSLIST");
while ($row=mysql_fetch_array($result)) {
  $process_id=$row["Id"];
    if ($row["Time"] > $MAX_TIME) {
      $sql="KILL $process_id";
      mysql_query($sql);
  }
}

Beachten Sie, dass dies den Nebeneffekt hat, dass alle inaktiven Clientverbindungen gezwungen werden, die Verbindung wiederherzustellen, nicht nur diejenigen, die sich schlecht verhalten.

Wenn jemand eine bessere Antwort hat, würde ich sie gerne hören.

Bearbeiten: Es besteht mindestens ein ernstes Risiko, KILLauf diese Weise zu verwenden. Angenommen, Sie verwenden das obige Skript für KILLjede Verbindung, die 10 Sekunden lang inaktiv ist. Nachdem eine Verbindung 10 Sekunden lang inaktiv war, aber bevor die KILLausgegeben wird, gibt der Benutzer, dessen Verbindung unterbrochen wird, einen START TRANSACTIONBefehl aus. Sie sind dann KILLed. Sie senden eine UPDATEund geben dann, zweimal überlegen, eine aus ROLLBACK. Da die KILLursprüngliche Transaktion jedoch zurückgesetzt wurde, wurde das Update sofort durchgeführt und kann nicht zurückgesetzt werden! In diesem Beitrag finden Sie einen Testfall.


2
Dieser Kommentar richtet sich an zukünftige Leser dieser Antwort. Bitte tun Sie dies nicht, auch wenn es die akzeptierte Antwort ist.
Hobodave

OK, anstatt abzustimmen und einen kurzen Kommentar zu hinterlassen, wie wäre es zu erklären, warum dies eine schlechte Idee wäre? Die Person, die das ursprüngliche PHP-Skript gepostet hat, sagte, dass es ihren Server davon abhält, zu hängen. Wenn es einen besseren Weg gibt, um dasselbe Ziel zu erreichen, zeigen Sie es mir.
Trevor Burnham

6
Der Kommentar ist nicht abfällig. Es ist eine Warnung für alle zukünftigen Leser, keinen Versuch zu unternehmen, Ihre "Lösung" zu verwenden. Langfristig laufende Transaktionen automatisch zu beenden, ist eine einseitig schreckliche Idee. Es sollte niemals gemacht werden . Das ist die Erklärung. Tötungssitzungen sollten eine Ausnahme sein, keine Norm. Die Hauptursache für Ihr erfundenes Problem ist ein Benutzerfehler. Sie erstellen keine technischen Lösungen für Datenbankadministratoren, die Zeilen sperren und dann "weggehen". Du feuerst sie.
Hobodave

-2

Wie Hobodave in einem Kommentar vorgeschlagen hat, ist es bei einigen Clients (obwohl anscheinend nicht das mysqlBefehlszeilenprogramm) möglich, ein Zeitlimit für eine Transaktion festzulegen. Hier ist eine Demonstration mit ActiveRecord in Ruby:

require 'rubygems'
require 'timeout'
require 'active_record'

Timeout::timeout(5) {
  Foo.transaction do
    Foo.create(:name => 'Bar')
    sleep 10
  end
}

In diesem Beispiel läuft die Transaktion nach 5 Sekunden ab und wird automatisch zurückgesetzt . ( Aktualisierung als Antwort auf den Kommentar von hobodave: Wenn die Datenbank länger als 5 Sekunden benötigt, um zu antworten, wird die Transaktion sofort zurückgesetzt - nicht früher.) Wenn Sie sicherstellen möchten , dass alle Ihre Transaktionen nach Ablauf der Zeit ablaufenn Sekunden ablaufen, Sie können einen Wrapper um ActiveRecord erstellen. Ich vermute, dass dies auch für die beliebtesten Bibliotheken in Java, .NET, Python usw. gilt, aber ich habe sie noch nicht getestet. (Wenn ja, schreiben Sie bitte einen Kommentar zu dieser Antwort.)

Transaktionen in ActiveRecord haben im KILLGegensatz zu Transaktionen über die Befehlszeile auch den Vorteil, dass sie sicher sind, wenn a ausgegeben wird. Siehe /dba/1561/mysql-client-believes-theyre-in-a-transaction-gets-killed-wreaks-havoc .

Es scheint nicht möglich zu sein, eine maximale Transaktionszeit auf der Serverseite durchzusetzen, außer durch ein Skript wie das, das ich in meiner anderen Antwort gepostet habe.


Sie haben übersehen, dass ActiveRecord synchrone (blockierende) E / A verwendet. Das Skript unterbricht lediglich den Ruby-Schlafbefehl. Wenn Ihr Foo.createBefehl 5 Minuten lang blockiert würde, würde das Timeout ihn nicht beenden. Dies ist nicht kompatibel mit dem in Ihrer Frage angegebenen Beispiel. A SELECT ... FOR UPDATEkönnte leicht für lange Zeiträume blockieren, ebenso wie jede andere Abfrage.
Hobodave

Es sollte beachtet werden, dass fast alle MySQL-Client-Bibliotheken blockierende E / A verwenden, daher der Vorschlag in meiner Antwort, dass Sie Ihre eigenen verwenden oder schreiben müssten, die nicht blockierende E / A verwenden. Hier ist eine einfache Demonstration, dass das Timeout nutzlos ist: gist.github.com/856100
Hobodave

In meiner Frage ging es mir eigentlich mehr um ein Szenario, in dem die Client-App im Verlauf einer Transaktion hängt (z. B. in einer Endlosschleife gefangen wird), als um ein Szenario, in dem die Transaktion am Ende von MySQL lange dauert. Aber danke, das ist ein guter Punkt. Ich habe die Frage aktualisiert, um sie zu notieren.)
Trevor Burnham

Ich kann Ihre behaupteten Ergebnisse nicht duplizieren. Ich habe das Gist aktualisiert, um Folgendes zu verwenden ActiveRecord::Base#find_by_sql: gist.github.com/856100 Dies liegt wiederum daran, dass AR blockierende E / A verwendet.
Hobodave

@hobodave Ja, diese Bearbeitung war fehlerhaft und wurde korrigiert. Um es klar auszudrücken: Die timeoutMethode setzt die Transaktion zurück, jedoch erst, wenn die MySQL-Datenbank auf eine Abfrage antwortet. Die timeoutMethode eignet sich daher zum Verhindern langer Transaktionen auf der Clientseite oder langer Transaktionen, die aus mehreren relativ kurzen Abfragen bestehen, jedoch nicht für lange Transaktionen, bei denen eine einzelne Abfrage der Schuldige ist.
Trevor Burnham
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.