Mysql: Comportamento estranho com cancelamento de consulta usando contexto

Criado em 2 out. 2018  ·  19Comentários  ·  Fonte: go-sql-driver/mysql

Servidor: MySQL 5.6

Processo Armazenado:

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

Código Go:

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

Comentários muito úteis

Podemos usar SHOW FULL PROCESSLIST e KILL para enviar um KILL para a consulta correta.

Infelizmente, isso não pode ser implementado facilmente no nível do driver. Isso exigiria uma segunda conexão na qual o driver executasse esses comandos. Isso pode ser uma "conexão de gerenciamento" compartilhada ou conexões abertas sob demanda.

Em primeiro lugar, isso tornaria o driver muito mais complexo. Neste momento todo o tratamento de sessões e conexões é feito pelo pacote database/sql , não pelo driver, que apenas fornece as funções para abrir uma nova conexão etc.

Mas muito mais problemático é que, na verdade, não temos como encontrar o servidor certo (a menos que haja apenas um). O driver é amplamente implantado com proxies de balanceamento de carga mysql. Nesse caso, é provável que SHOW FULL PROCESSLIST seja executado no servidor errado e não possamos encontrar a consulta.

Todos 19 comentários

Estamos encontrando um comportamento estranho.
Quando executamos slowInsert( 10 ) em um cliente mysql (por exemplo, sequel pro), a espera de 10 segundos é respeitada antes da inserção. Não temos motivos para acreditar que o proc armazenado está errado ou inválido.

Quando executamos o código Go acima, o contexto é cancelado após 3 segundos -- tudo bem.
... Mas imediatamente ocorre a inserção. (Não espera 10 segundos).

Agora, se alterarmos o proc armazenado para que seja:

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 ;

O código Go funciona perfeitamente e cancela a consulta (ou seja, nenhuma inserção acontece).
Em ambos os casos, nosso cliente mysql (sequel pro) respeita o período de espera, portanto, ambos os procs armazenados são equivalentes.

Mas em nosso código Go, o segundo proc armazenado funciona, mas o primeiro não - levando a nossa crença de que há algum bug estranho no driver.

@alessio-palumbo

Não é um bug. Ainda não oferecemos suporte ao cancelamento de consulta.
E SLEEP() é uma consulta muito especial. Ele verifica se a conexão está ativa ou não.
Em casos gerais, o MySQL não verifica a conexão até que a consulta seja concluída.

O Leiame: 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 é enganoso. Apenas cancelando o resultado da consulta "aguardando", não a execução da consulta.

O protocolo MySQL não fornece uma maneira segura de cancelar a execução da consulta.
Alguns CLI suportam o cancelamento, mas não são seguros/estáveis ​​em alguns ambientes.

obrigado

O que você deve fazer é usar transação. Se você não confirmar, é revertido.
Portanto, o tempo limite enquanto a transação é segura.

@andizzle @alexclifford @edwardhutchison

Por que o primeiro storedProc é inserido após o tempo limite, mas o segundo storedProc não é inserido. Eu diria que, com base em sua declaração, ele insere ou não. Se o driver não suporta o cancelamento da execução da consulta, por que parece cancelar a execução?

É o comportamento do MySQL. Eu não sei muito. Mas SLEEP() tem um comportamento muito estranho quando a conexão é fechada.

@julienschmidt o que você acha dessa ideia: https://github.com/src-d/gitbase-web/issues/241#issuecomment -427740194

Podemos usar SHOW FULL PROCESSLIST e KILL para enviar um KILL para a consulta correta.

Infelizmente, isso não pode ser implementado facilmente no nível do driver. Isso exigiria uma segunda conexão na qual o driver executasse esses comandos. Isso pode ser uma "conexão de gerenciamento" compartilhada ou conexões abertas sob demanda.

Em primeiro lugar, isso tornaria o driver muito mais complexo. Neste momento todo o tratamento de sessões e conexões é feito pelo pacote database/sql , não pelo driver, que apenas fornece as funções para abrir uma nova conexão etc.

Mas muito mais problemático é que, na verdade, não temos como encontrar o servidor certo (a menos que haja apenas um). O driver é amplamente implantado com proxies de balanceamento de carga mysql. Nesse caso, é provável que SHOW FULL PROCESSLIST seja executado no servidor errado e não possamos encontrar a consulta.

Você seria capaz de fazer uma proposta Go 2 para modificar o que for necessário para database/sql para tornar uma consulta realmente cancelável?

Sinceramente, acho que não database/sql ou este driver é o problema aqui, mas o próprio MySQL simples, que não fornece

Um bom começo seria se a resposta às consultas enviadas ao servidor contivesse o id do processo.

No caso de uma configuração de servidor único, poderíamos cancelar as consultas de maneira semelhante ao Postgres : Abra uma nova conexão e envie imediatamente o comando KILL .

O caso com algum proxy é mais difícil. No momento, esses proxies são completamente transparentes para o motorista. O que seria necessário é algum tipo de mecanismo para garantir que uma conexão seja aberta para o mesmo backend de outra conexão existente.

Na verdade, já temos um PR aberto para uma abordagem semelhante (para o caso de configuração de servidor único): https://github.com/go-sql-driver/mysql/pull/791

Vou enviar uma solicitação de recurso com o MySQL. Se eu pedir que as consultas contenham o ID do processo, isso criaria uma alteração incompatível com versões anteriores que eles recusariam?

Não é o único problema de cancelar. Existem algumas outras questões.
Não há nenhum protocolo de "cancelamento confiável em remoto" na Terra.
Deve ser usado apenas para salvar algumas CPUs.

De qualquer forma, qual é o ponto desta questão?
O comportamento estranho não é evitado com a implementação do cancelamento.
Você não pode saber que a consulta "cancelada por contexto" é executada ou cancelada no servidor MySQL. É questão de tempo.
Só você pode fazer é usar transação; todas as consultas serão revertidas.

Se realmente precisarmos de um problema para "Implementar consulta de cancelamento", registre um novo problema e feche-o.

Aqui está minha solução: https://medium.com/@rocketlaunchr.cloud/canceling -mysql-in-go-827ed8f83b30

Esta página foi útil?
0 / 5 - 0 avaliações