Mysql: Seltsames Verhalten bei Abfrageabbruch mit Kontext

Erstellt am 2. Okt. 2018  ·  19Kommentare  ·  Quelle: go-sql-driver/mysql

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)
    }
}

Hilfreichster Kommentar

Wir können SHOW FULL PROCESSLIST und KILL , um KILL 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.

Alle 19 Kommentare

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.

Wir können SHOW FULL PROCESSLIST und KILL , um KILL 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

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen