Server: MySQL 5.6
Gespeicherter Vorgang:
DELIMITER //
CREATE PROCEDURE slowInsert(IN t int)
BEGIN
SELECT SLEEP(t);
INSERT INTO `table_x` (message) VALUES (UUID());
END //
DELIMITER ;
Go-Code:
package main
import (
"context"
"database/sql"
"time"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "<url>")
if err != nil {
panic(err)
}
db.SetConnMaxLifetime(9 * time.Second)
db.SetMaxIdleConns(12)
db.SetMaxOpenConns(12)
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, time.Duration(3)*time.Second)
defer cancel()
_, err = db.ExecContext(ctx, "call slowInsert( 10 )") // context will cancel before insert occurs
if err != nil {
panic(err)
}
}
Wir stoßen auf seltsames Verhalten.
Wenn wir slowInsert( 10 )
auf einem MySQL-Client (zB Sequel Pro) ausführen, wird die Wartezeit von 10 Sekunden vor dem Einfügen berücksichtigt. Wir haben keinen Grund zu der Annahme, dass der gespeicherte Vorgang falsch oder ungültig ist.
Wenn wir den obigen Go-Code ausführen, wird der Kontext nach 3 Sekunden abgebrochen - alles gut.
... Aber sofort erfolgt das Einfügen. (Es wartet nicht 10 Sekunden).
Wenn wir nun den gespeicherten Vorgang so ändern, dass er:
DELIMITER //
CREATE PROCEDURE slowInsert(IN t int)
BEGIN
SELECT SLEEP(t/2); // <---- The change
SELECT SLEEP(t/2); // <---- The change
INSERT INTO `table_x` (message) VALUES (UUID());
END //
DELIMITER ;
Der Go-Code funktioniert einwandfrei und bricht die Abfrage ab (dh es erfolgt keine Einfügung).
In beiden Fällen respektiert unser MySQL-Client (Sequel Pro) die Wartezeit, daher sind beide gespeicherten Prozesse gleichwertig.
Aber in unserem Go-Code funktioniert der zweite gespeicherte Vorgang, der erste jedoch nicht - was zu unserer Annahme führt, dass der Treiber einen seltsamen Fehler enthält.
@ alessio-palumbo
Es ist kein Fehler. Das Abbrechen von Abfragen wird noch nicht unterstützt.
Und SLEEP()
ist eine sehr spezielle Abfrage. Es prüft, ob die Verbindung aktiv ist oder nicht.
Im Allgemeinen überprüft MySQL die Verbindung nicht, bis die Abfrage abgeschlossen ist.
Die Readme: https://github.com/go-sql-driver/mysql#contextcontext -support
Go 1.8 added database/sql support for context.Context. This driver supports query timeouts and cancellation via contexts. See context support in the database/sql package for more details.
OK, README ist irreführend. Es bricht nur das "warten" Abfrageergebnis ab, nicht die Abfrageausführung.
Das MySQL-Protokoll bietet keine sichere Möglichkeit, die Abfrageausführung abzubrechen.
Einige CLI unterstützen das Abbrechen, aber es ist in einigen Umgebungen nicht sicher/stabil.
Danke
Was Sie tun sollten, ist die Transaktion. Wenn Sie sich nicht festlegen, wird es zurückgesetzt.
Timeout während der Transaktion ist also sicher.
@andizzle @alexclifford @edwardhutchison
Warum fügt der erste StoredProc nach dem Timeout ein, aber der zweite StoredProc fügt sich nicht ein. Ich würde davon ausgehen, dass basierend auf Ihrer Aussage entweder eingefügt wird oder nicht. Wenn der Treiber das Abbrechen der Abfrageausführung nicht unterstützt, warum scheint er die Ausführung abzubrechen?
Es ist das MySQL-Verhalten. Ich weiß nicht viel. Aber SLEEP() hat ein sehr seltsames Verhalten, wenn die Verbindung geschlossen wird.
@julienschmidt was https://github.com/src-d/gitbase-web/issues/241#issuecomment -427740194
Wir können
SHOW FULL PROCESSLIST
undKILL
, umKILL
an die richtige Abfrage zu senden.
Leider ist das auf Treiberebene nicht ohne weiteres umsetzbar. Es würde eine 2. Verbindung erfordern, auf der der Treiber diese Befehle ausführt. Das kann entweder eine gemeinsam genutzte "Verwaltungsverbindung" oder Verbindungen sein, die bei Bedarf geöffnet werden.
Dies würde den Fahrer zunächst viel komplexer machen. Im Moment wird die gesamte Handhabung von Sitzungen und Verbindungen vom Paket database/sql
, nicht vom Treiber, der nur die Funktionen zum Öffnen einer neuen Verbindung usw. bereitstellt.
Viel problematischer ist jedoch, dass wir eigentlich keine Möglichkeit haben, den richtigen Server zu finden (es sei denn, es gibt nur einen). Der Treiber wird größtenteils mit MySQL-Load-Balancing-Proxys bereitgestellt. In diesem Fall ist es wahrscheinlich, dass SHOW FULL PROCESSLIST
auf dem falschen Server ausgeführt wird und wir die Abfrage nicht finden können.
Wären Sie in der Lage, einen Go 2-Vorschlag zu machen, um alles, was erforderlich ist, in database/sql
zu ändern, um eine Abfrage wirklich stornierbar zu machen?
Ich denke ehrlich gesagt, dass hier nicht database/sql
oder dieser Treiber das Problem ist, sondern einfaches MySQL selbst, das keine einfache Möglichkeit bietet
Ein guter Anfang wäre, wenn die Antwort auf Anfragen an den Server die Prozess-ID enthalten würde.
Im Fall eines einzelnen Server-Setups könnten wir dann Abfragen ähnlich wie bei Postgres abbrechen : Öffnen Sie eine neue Verbindung und senden Sie sofort den Befehl KILL
.
Der Fall mit einigen Proxys ist schwieriger. Im Moment sind solche Proxys für den Fahrer völlig transparent. Was erforderlich wäre, wäre eine Art Mechanismus, um sicherzustellen, dass eine Verbindung zum gleichen Back-End wie eine andere bestehende Verbindung geöffnet wird.
Wir haben tatsächlich bereits eine offene PR für einen ähnlichen Ansatz (für den Fall der Einrichtung eines einzelnen Servers): https://github.com/go-sql-driver/mysql/pull/791
Ich werde eine Feature-Anfrage mit MySQL einreichen. Wenn ich darum bitte, dass Abfragen die Prozess-ID enthalten, würde dies zu einer abwärtsinkompatiblen Änderung führen, die sie komplett ablehnen?
Das ist nicht nur das Problem der Stornierung. Es gibt einige andere Probleme.
Es gibt kein Protokoll "zuverlässiges Abbrechen auf Remote" auf der Erde.
Es sollte nur zum Speichern einiger CPUs verwendet werden.
Wie auch immer, was ist der Sinn dieses Problems?
Seltsames Verhalten wird durch die Implementierung einer Stornierung nicht vermieden.
Sie können nicht wissen, dass die Abfrage "vom Kontext abgebrochen" im MySQL-Server ausgeführt oder abgebrochen wird. Es ist ein Timing-Problem.
Nur Sie können die Transaktion verwenden; alle Abfragen werden zurückgesetzt.
Wenn wir wirklich ein Problem für "Abbrechen der Abfrage implementieren" benötigen, reichen Sie ein neues Problem ein und schließen Sie dieses.
Hier ist meine Lösung: https://medium.com/@rocketlaunchr.cloud/canceling -mysql-in-go-827ed8f83b30
Hilfreichster Kommentar
Leider ist das auf Treiberebene nicht ohne weiteres umsetzbar. Es würde eine 2. Verbindung erfordern, auf der der Treiber diese Befehle ausführt. Das kann entweder eine gemeinsam genutzte "Verwaltungsverbindung" oder Verbindungen sein, die bei Bedarf geöffnet werden.
Dies würde den Fahrer zunächst viel komplexer machen. Im Moment wird die gesamte Handhabung von Sitzungen und Verbindungen vom Paket
database/sql
, nicht vom Treiber, der nur die Funktionen zum Öffnen einer neuen Verbindung usw. bereitstellt.Viel problematischer ist jedoch, dass wir eigentlich keine Möglichkeit haben, den richtigen Server zu finden (es sei denn, es gibt nur einen). Der Treiber wird größtenteils mit MySQL-Load-Balancing-Proxys bereitgestellt. In diesem Fall ist es wahrscheinlich, dass
SHOW FULL PROCESSLIST
auf dem falschen Server ausgeführt wird und wir die Abfrage nicht finden können.