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)
}
}
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
eKILL
para enviar umKILL
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
Comentários muito úteis
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.