Mysql: Comportement étrange avec annulation de requête en utilisant le contexte

Créé le 2 oct. 2018  ·  19Commentaires  ·  Source: go-sql-driver/mysql

Serveur : MySQL 5.6

Procédure stockée :

DELIMITER //
CREATE PROCEDURE slowInsert(IN t int)
BEGIN       
       SELECT SLEEP(t);
       INSERT INTO `table_x` (message) VALUES (UUID());
END //
DELIMITER ;

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

Commentaire le plus utile

Nous pourrions peut-être utiliser SHOW FULL PROCESSLIST et KILL pour envoyer un KILL à la bonne requête.

Malheureusement, cela ne peut pas être mis en œuvre facilement au niveau du pilote. Il faudrait une 2ème connexion sur laquelle le pilote exécute ces commandes. Il peut s'agir d'une "connexion de gestion" partagée ou de connexions ouvertes à la demande.

Tout d'abord, cela rendrait le pilote beaucoup plus complexe. À l'heure actuelle, toute la gestion des sessions et des connexions est effectuée par le package database/sql , et non par le pilote, qui fournit simplement les fonctions pour ouvrir une nouvelle connexion, etc.

Mais ce qui est beaucoup plus problématique, c'est que nous n'avons en fait aucun moyen de trouver le bon serveur (à moins qu'il n'y en ait qu'un). Le pilote est largement déployé avec des proxys d'équilibrage de charge mysql. Dans ce cas, il est probable que SHOW FULL PROCESSLIST soit exécuté sur le mauvais serveur et que nous ne puissions pas trouver la requête.

Tous les 19 commentaires

Nous sommes confrontés à des comportements étranges.
Lorsque nous exécutons slowInsert( 10 ) sur un client mysql (ex. sequel pro), le délai de 10 secondes est respecté avant l'insertion. Nous n'avons aucune raison de croire que le processus stocké est erroné ou invalide.

Lorsque nous exécutons le code Go ci-dessus, le contexte s'annule après 3 secondes - tout va bien.
... Mais immédiatement l'insertion se produit. (Il n'attend pas 10 secondes).

Maintenant, si nous modifions le proc stocké pour qu'il soit :

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 ;

Le code Go fonctionne parfaitement et annule la requête (c'est-à-dire qu'aucune insertion ne se produit).
Dans les deux cas, notre client mysql (sequel pro) respecte la période d'attente donc les deux procs stockés sont équivalents.

Mais dans notre code Go, le deuxième proc stocké fonctionne mais pas le premier - ce qui nous amène à croire qu'il y a un bogue étrange dans le pilote.

@ alessio-palumbo

Ce n'est pas un bug. Nous ne prenons pas encore en charge l'annulation de la requête.
Et SLEEP() est une requête très spéciale. Il vérifie que la connexion est active ou non.
En général, MySQL ne vérifie pas la connexion tant que la requête n'est pas terminée.

Le fichier Lisez-moi : 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 est trompeur. Il annule simplement le résultat de la requête "en attente", pas l'exécution de la requête.

Le protocole MySQL ne fournit pas de moyen sûr d'annuler l'exécution d'une requête.
Certaines CLI prennent en charge l'annulation, mais ce n'est pas sûr/stable sur certains environnements.

Merci

Ce que vous devez faire, c'est utiliser la transaction. Si vous ne vous engagez pas, il est annulé.
Donc timeout pendant que la transaction est sûre.

@andizzle @alexclifford @edwardhutchison

Pourquoi le premier stockéProc s'insère-t-il après le délai d'attente mais le deuxième stockéProc ne s'insère pas. Je suppose que sur la base de votre déclaration, il s'insère ou non. Si le pilote ne prend pas en charge l'annulation de l'exécution de la requête, pourquoi semble-t-il annuler l'exécution ?

C'est le comportement de MySQL. Je ne sais pas grand chose. Mais SLEEP() a un comportement très étrange lorsque la connexion est fermée.

@julienschmidt que pensez-vous de cette idée : https://github.com/src-d/gitbase-web/issues/241#issuecomment -427740194

Nous pourrions peut-être utiliser SHOW FULL PROCESSLIST et KILL pour envoyer un KILL à la bonne requête.

Malheureusement, cela ne peut pas être mis en œuvre facilement au niveau du pilote. Il faudrait une 2ème connexion sur laquelle le pilote exécute ces commandes. Il peut s'agir d'une "connexion de gestion" partagée ou de connexions ouvertes à la demande.

Tout d'abord, cela rendrait le pilote beaucoup plus complexe. À l'heure actuelle, toute la gestion des sessions et des connexions est effectuée par le package database/sql , et non par le pilote, qui fournit simplement les fonctions pour ouvrir une nouvelle connexion, etc.

Mais ce qui est beaucoup plus problématique, c'est que nous n'avons en fait aucun moyen de trouver le bon serveur (à moins qu'il n'y en ait qu'un). Le pilote est largement déployé avec des proxys d'équilibrage de charge mysql. Dans ce cas, il est probable que SHOW FULL PROCESSLIST soit exécuté sur le mauvais serveur et que nous ne puissions pas trouver la requête.

Seriez-vous en mesure de faire une proposition Go 2 pour modifier ce qui est nécessaire à database/sql pour rendre une requête vraiment annulable ?

Je pense honnêtement que ce n'est pas database/sql ou ce pilote qui pose problème ici, mais simplement MySQL lui-même, qui ne fournit

Un bon début serait que la réponse aux requêtes envoyées au serveur contienne l'ID de processus.

Dans le cas d'une configuration de serveur unique, nous pourrions alors annuler les requêtes de la même manière que Postgres : Ouvrir une nouvelle connexion et envoyer immédiatement la commande KILL .

Le cas avec certains proxy est plus difficile. À l'heure actuelle, ces proxys sont complètement transparents pour le conducteur. Ce qui serait nécessaire, c'est une sorte de mécanisme pour garantir qu'une connexion est ouverte au même backend qu'une autre connexion existante.

En fait, nous avons déjà un PR ouvert pour une approche similaire (pour le cas de la configuration d'un seul serveur) : https://github.com/go-sql-driver/mysql/pull/791

Je vais soumettre une demande de fonctionnalité avec MySQL. Si je demande que des requêtes contiennent l'identifiant du processus, cela créerait-il un changement incompatible avec l'arrière qu'ils refuseraient catégoriquement ?

Ce n'est pas seulement le problème de l'annulation. Il y a d'autres problèmes.
Il n'y a pas de protocole "d'annulation fiable à distance" sur terre.
Il ne doit être utilisé que pour économiser certains processeurs.

Quoi qu'il en soit, quel est l'intérêt de ce problème?
Les comportements étranges ne sont pas évités en implémentant l'annulation.
Vous ne pouvez pas savoir que la requête « annulée par le contexte » est exécutée ou annulée dans le serveur MySQL. C'est une question de timing.
Vous seul pouvez utiliser la transaction ; toutes les requêtes seront annulées.

Si nous avons vraiment besoin d'un problème pour "Implémenter la requête d'annulation", déposez un nouveau problème et fermez-le.

Voici ma solution : https://medium.com/@rocketlaunchr.cloud/canceling -mysql-in-go-827ed8f83b30

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

AlekSi picture AlekSi  ·  3Commentaires

tbdingqi picture tbdingqi  ·  7Commentaires

zhaohui-kevin picture zhaohui-kevin  ·  5Commentaires

albrow picture albrow  ·  7Commentaires

Dieterbe picture Dieterbe  ·  6Commentaires