Mysql: Comportamiento extraño con la cancelación de consultas usando contexto

Creado en 2 oct. 2018  ·  19Comentarios  ·  Fuente: go-sql-driver/mysql

Servidor: MySQL 5.6

Proceso almacenado:

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

Ir Código:

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

Comentario más útil

Podríamos usar SHOW FULL PROCESSLIST y KILL para enviar un KILL a la consulta correcta.

Desafortunadamente, eso no se puede implementar fácilmente en el nivel del controlador. Requeriría una segunda conexión en la que el controlador ejecuta estos comandos. Podría ser una "conexión de administración" compartida o conexiones abiertas bajo demanda.

En primer lugar, esto haría que el controlador fuera mucho más complejo. En este momento, todo el manejo de sesiones y conexiones lo realiza el paquete database/sql , no el controlador, que solo proporciona las funciones para abrir una nueva conexión, etc.

Pero mucho más problemático es que en realidad no tenemos forma de encontrar el servidor correcto (a menos que solo haya uno). El controlador se implementa en gran medida con proxies de balanceo de carga mysql. En ese caso, es probable que SHOW FULL PROCESSLIST se ejecute en el servidor incorrecto y no podamos encontrar la consulta.

Todos 19 comentarios

Nos encontramos con un comportamiento extraño.
Cuando ejecutamos slowInsert( 10 ) en un cliente mysql (por ejemplo, sequel pro), se respeta la espera de 10 segundos antes de la inserción. No tenemos motivos para creer que el proceso almacenado es incorrecto o inválido.

Cuando ejecutamos el código Go anterior, el contexto se cancela después de 3 segundos, todo bien.
... Pero inmediatamente se produce la inserción. (No espera 10 segundos).

Ahora, si cambiamos el proceso almacenado para que sea:

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 ;

El código Go funciona perfectamente y cancela la consulta (es decir, no se produce ninguna inserción).
En ambos casos, nuestro cliente mysql (sequel pro) respeta el período de espera, por lo que ambos procesos almacenados son equivalentes.

Pero en nuestro código Go, el segundo proceso almacenado funciona pero el primero no, lo que nos lleva a creer que hay algún error extraño en el controlador.

@ alessio-palumbo

No es un error. Todavía no admitimos la cancelación de consultas.
Y SLEEP() es una consulta muy especial. Comprueba que la conexión está activa o no.
En general, MySQL no verifica la conexión hasta que finaliza la consulta.

Léame: 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 es engañoso. Simplemente cancela el resultado de la consulta "en espera", no la ejecución de la consulta.

El protocolo MySQL no proporciona una forma segura de cancelar la ejecución de consultas.
Algunas CLI admiten la cancelación, pero no son seguras ni estables en algunos entornos.

Gracias

Lo que debe hacer es usar la transacción. Si no se compromete, se revierte.
Por lo tanto, el tiempo de espera mientras la transacción es segura.

@andizzle @alexclifford @edwardhutchison

¿Por qué el primer proceso almacenado se inserta después del tiempo de espera pero el segundo proceso almacenado no se inserta? Supongo que, según su declaración, se inserta o no. Si el controlador no admite la cancelación de la ejecución de consultas, ¿por qué parece cancelar la ejecución?

Es el comportamiento de MySQL. no se mucho Pero SLEEP() tiene un comportamiento muy extraño cuando se cierra la conexión.

@julienschmidt, ¿qué opinas de esta idea?: https://github.com/src-d/gitbase-web/issues/241#issuecomment -427740194

Podríamos usar SHOW FULL PROCESSLIST y KILL para enviar un KILL a la consulta correcta.

Desafortunadamente, eso no se puede implementar fácilmente en el nivel del controlador. Requeriría una segunda conexión en la que el controlador ejecuta estos comandos. Podría ser una "conexión de administración" compartida o conexiones abiertas bajo demanda.

En primer lugar, esto haría que el controlador fuera mucho más complejo. En este momento, todo el manejo de sesiones y conexiones lo realiza el paquete database/sql , no el controlador, que solo proporciona las funciones para abrir una nueva conexión, etc.

Pero mucho más problemático es que en realidad no tenemos forma de encontrar el servidor correcto (a menos que solo haya uno). El controlador se implementa en gran medida con proxies de balanceo de carga mysql. En ese caso, es probable que SHOW FULL PROCESSLIST se ejecute en el servidor incorrecto y no podamos encontrar la consulta.

¿Sería capaz de hacer una propuesta Go 2 para modificar lo que sea necesario a database/sql para que una consulta sea realmente cancelable?

Sinceramente, creo que el problema aquí no es database/sql ni este controlador, sino el propio MySQL, que no proporciona

Un buen comienzo sería si la respuesta a las consultas enviadas al servidor contuviera la identificación del proceso.

En el caso de una configuración de un solo servidor, podríamos cancelar consultas de manera similar a Postgres : Abra una nueva conexión e inmediatamente envíe el comando KILL .

El caso con algún proxy es más difícil. En este momento, tales proxies son completamente transparentes para el controlador. Lo que se requeriría es algún tipo de mecanismo para garantizar que una conexión se abra al mismo backend que otra conexión existente.

De hecho, ya tenemos un PR abierto para un enfoque similar (para el caso de configuración de un solo servidor): https://github.com/go-sql-driver/mysql/pull/791

Voy a enviar una solicitud de función con MySQL. Si solicito consultas que contengan la identificación del proceso, ¿eso crearía un cambio incompatible con versiones anteriores que rechazarán rotundamente?

No es el único problema de cancelar. Hay algunos otros problemas.
No hay ningún protocolo de "cancelación confiable en remoto" en la tierra.
Debe usarse solo para guardar algunas CPU.

De todos modos, ¿cuál es el punto de este problema?
El comportamiento extraño no se evita implementando la cancelación.
No puede saber que la consulta "cancelada por contexto" se ejecuta o cancela en el servidor MySQL. Es cuestión de tiempo.
Lo único que puedes hacer es usar la transacción; todas las consultas se revertirán.

Si realmente necesitamos un problema para "Implementar consulta de cancelación", presente un nuevo problema y cierre este.

Aquí está mi solución: https://medium.com/@rocketlaunchr.cloud/canceling -mysql-in-go-827ed8f83b30

¿Fue útil esta página
0 / 5 - 0 calificaciones