Isso surgiu a partir dessa pergunta relacionada , em que eu queria saber como forçar duas transações a ocorrerem seqüencialmente em um caso trivial (onde ambas estão operando em apenas uma única linha). Eu recebi uma resposta - use SELECT ... FOR UPDATE
como a primeira linha de ambas as transações - mas isso leva a um problema: se a primeira transação nunca for confirmada ou revertida, a segunda transação será bloqueada indefinidamente. A innodb_lock_wait_timeout
variável define o número de segundos após os quais o cliente que tenta fazer a segunda transação recebe a mensagem "Desculpe, tente novamente" ... mas, tanto quanto eu sei, eles tentariam novamente até o próximo servidor reiniciar. Então:
- Certamente deve haver uma maneira de forçar um
ROLLBACK
se uma transação estiver demorando para sempre? Devo recorrer ao uso de um daemon para eliminar essas transações? Em caso afirmativo, como seria esse daemon? - Se uma conexão é interrompida por
wait_timeout
ou nointeractive_timeout
meio da transação, a transação é revertida? Existe uma maneira de testar isso no console?
Esclarecimento : innodb_lock_wait_timeout
define o número de segundos que uma transação aguardará que um bloqueio seja liberado antes de desistir; o que eu quero é uma maneira de forçar um bloqueio a ser liberado.
Atualização 1 : Aqui está um exemplo simples que demonstra por que innodb_lock_wait_timeout
não é suficiente para garantir que a segunda transação não seja bloqueada pela primeira:
START TRANSACTION;
SELECT SLEEP(55);
COMMIT;
Com a configuração padrão de innodb_lock_wait_timeout = 50
, esta transação é concluída sem erros após 55 segundos. E se você adicionar um UPDATE
antes da SLEEP
linha, inicie uma segunda transação de outro cliente que tenta SELECT ... FOR UPDATE
a mesma linha, é a segunda transação que atinge o tempo limite, não a que adormeceu.
O que estou procurando é uma maneira de forçar o fim do sono repousante dessa transação.
Atualização 2 : em resposta às preocupações da hobodave sobre o quão realista é o exemplo acima, aqui está um cenário alternativo: um DBA se conecta a um servidor ativo e executa
START TRANSACTION
SELECT ... FOR UPDATE
onde a segunda linha trava uma linha na qual o aplicativo grava frequentemente. Em seguida, o DBA é interrompido e se afasta, esquecendo de finalizar a transação. O aplicativo é interrompido até a linha ser desbloqueada. Gostaria de minimizar o tempo em que o aplicativo está bloqueado como resultado desse erro.
ROLLBACK
na primeira transação, se demorar mais den
alguns segundos para concluir. Existe alguma maneira de fazer isso?MYSQL
não tem uma configuração para evitar esse cenário. Porque não é aceitável a interrupção do servidor devido à irresponsabilidade dos clientes. Não encontrei nenhuma dificuldade para entender sua pergunta também é tão relevante.Respostas:
Mais da metade desse segmento parece ser sobre como fazer perguntas no ServerFault. Acho que a pergunta faz sentido e é bastante simples: como você reverte automaticamente uma transação paralisada?
Uma solução, se você estiver disposto a eliminar toda a conexão, é definir wait_timeout / interactive_timeout. Consulte /programming/9936699/mysql-rollback-on-transaction-with-lost-disconnected-connection .
fonte
Como sua pergunta é feita aqui no ServerFault, é lógico supor que você esteja procurando uma solução MySQL para um problema do MySQL, particularmente no campo do conhecimento em que um administrador de sistema e / ou um DBA teriam experiência. Como tal, os seguintes A seção aborda suas perguntas:
Não, não vai. Eu acho que você não está entendendo
innodb_lock_wait_timeout
. Faz exatamente o que você precisa.Ele retornará com um erro, conforme indicado no manual:
innodb_lock_wait_timeout
segundos.Por padrão, a transação não será revertida. É de responsabilidade do código do aplicativo decidir como lidar com esse erro, seja tentando novamente ou revertendo.
Se você deseja uma reversão automática, isso também é explicado no manual:
RE: Suas inúmeras atualizações e comentários
Primeiro, você declarou em seus comentários que "pretendia" dizer que deseja uma maneira de atingir o tempo limite da primeira transação que está bloqueando indefinidamente. Isso não é aparente na sua pergunta original e entra em conflito com "Se a primeira transação nunca for confirmada ou revertida, a segunda transação será bloqueada indefinidamente".
No entanto, também posso responder a essa pergunta. O protocolo MySQL não possui um "tempo limite de consulta". Isso significa que você não pode atingir o tempo limite da primeira transação bloqueada. Você deve esperar até que esteja terminado ou matar a sessão. Quando a sessão é encerrada, o servidor reverte automaticamente a transação.
A única outra alternativa seria usar ou escrever uma biblioteca mysql que utiliza E / S sem bloqueio, o que permitiria que seu aplicativo eliminasse o thread / fork fazendo a consulta após N segundos. A implementação e o uso dessa biblioteca estão além do escopo do ServerFault. Esta é uma pergunta apropriada para StackOverflow .
Em segundo lugar, você declarou o seguinte em seus comentários:
Isso não ficou aparente na sua pergunta original e ainda não é. Isso só pôde ser discernido depois que você compartilhou esse detalhe bastante importante no comentário.
Se esse é realmente o problema que você está tentando resolver, receio que você o tenha solicitado no fórum errado. Você descreveu um problema de programação no nível do aplicativo que requer uma solução de programação que o MySQL não pode fornecer e está fora do escopo desta comunidade. Sua resposta mais recente responde à pergunta "Como evito que um programa Ruby faça um loop infinito?". Essa pergunta é fora de tópico para esta comunidade e deve ser feita no StackOverflow .
fonte
innodb_lock_wait_timeout
sugestão), não é assim na situação que eu coloquei: onde o cliente trava ou trava antes de fazer a alteraçãoCOMMIT
ouROLLBACK
.COMMIT
ouROLLBACK
mensagem é enviada para resolver a primeira transação? Hobodave, talvez seja útil se você apresentar seu código de teste.Em uma situação semelhante, minha equipe decidiu usar pt-kill ( https://www.percona.com/doc/percona-toolkit/2.1/pt-kill.html ). Ele pode relatar ou eliminar consultas que (entre outras coisas) estão sendo executadas por muito tempo. É então possível executá-lo em um cron a cada X minutos.
O problema era que uma consulta que teve um tempo de execução inesperadamente longo (por exemplo, indefinido) bloqueou um procedimento de backup que tentou liberar tabelas com bloqueio de leitura, que, por sua vez, bloqueou todas as outras consultas sobre "aguardando a liberação da tabela", que nunca expirou. porque eles não estão aguardando um bloqueio, o que resultou em nenhum erro no aplicativo, o que resultou em nenhum alerta para os administradores e a interrupção do aplicativo.
A correção foi obviamente para corrigir a consulta inicial, mas para evitar que a situação aconteça acidentalmente no futuro, seria ótimo se fosse possível matar automaticamente (ou reportar) transações / consultas que duram mais do que o tempo específico, qual autor da pergunta parece estar perguntando. Isso é possível com pt-kill.
fonte
Uma solução simples será ter um procedimento armazenado para eliminar as consultas que levam mais tempo que o
timeout
tempo necessário e usar ainnodb_rollback_on_timeout
opção junto com ele.fonte
Depois de pesquisar no Google por um tempo, parece que não há maneira direta de definir um limite de tempo por transação. Aqui está a melhor solução que consegui encontrar:
O comando
fornece uma tabela com todos os comandos em execução no momento e o tempo (em segundos) desde que foram iniciados. Quando um cliente para de dar comandos, eles são descritos como dando um
Sleep
comando, com aTime
coluna sendo o número de segundos desde que o último comando foi concluído. Portanto, você pode entrar manualmente eKILL
tudo o que tiver umTime
valor acima de, digamos, 5 segundos, se tiver certeza de que nenhuma consulta deve levar mais de 5 segundos no banco de dados. Por exemplo, se o processo com o ID 3 tiver um valor 12 naTime
coluna, você poderá fazerA documentação para a sintaxe KILL sugere que uma transação sendo executada pelo encadeamento com esse ID seria revertida (embora eu não tenha testado isso, tenha cuidado).
Mas como automatizar isso e eliminar todos os scripts de horas extras a cada 5 segundos? O primeiro comentário nessa mesma página nos dá uma dica, mas o código está em PHP; parece que não podemos fazer um
SELECT
onSHOW PROCESSLIST
de dentro do cliente MySQL. Ainda assim, supondo que tenhamos um daemon PHP que executamos a cada$MAX_TIME
segundo, pode ser algo como isto:Observe que isso teria o efeito colateral de forçar a conexão de todas as conexões ociosas do cliente, não apenas aquelas que estão se comportando mal.
Se alguém tiver uma resposta melhor, eu adoraria ouvi-la.
Edit: Existe pelo menos um risco sério de usar
KILL
dessa maneira. Suponha que você use o script acima paraKILL
todas as conexões inativas por 10 segundos. Após uma conexãoKILL
ficar inativa por 10 segundos, mas antes da emissão, o usuário cuja conexão está sendo encerrada emite umSTART TRANSACTION
comando. Eles são entãoKILL
ed. Eles enviam umUPDATE
e, pensando duas vezes, emitem aROLLBACK
. No entanto, como aKILL
reversão da transação original foi revertida, a atualização foi realizada imediatamente e não pode ser revertida! Veja esta postagem para um caso de teste.fonte
Como hobodave sugeriu em um comentário, é possível em alguns clientes (embora não seja, aparentemente, o
mysql
utilitário de linha de comando) definir um limite de tempo para uma transação. Aqui está uma demonstração usando o ActiveRecord no Ruby:Neste exemplo, a transação atinge o tempo limite após 5 segundos e é revertida automaticamente . ( Atualize em resposta ao comentário do hobodave: se o banco de dados levar mais de 5 segundos para responder, a transação será revertida assim que acontecer - e não antes). Se você deseja garantir que todas as suas transações expirem após
n
segundos, você pode criar um wrapper em torno do ActiveRecord. Suponho que isso também se aplique às bibliotecas mais populares em Java, .NET, Python, etc., mas ainda não as testei. (Se tiver, poste um comentário sobre esta resposta.)As transações no ActiveRecord também têm a vantagem de serem seguras se uma
KILL
é emitida, ao contrário das transações feitas na linha de comando. Consulte /dba/1561/mysql-client-believes-theyre-in-a-transaction-gets-killed-wreaks-havoc .Não parece possível impor um tempo máximo de transação no servidor, exceto por meio de um script como o que eu publiquei na minha outra resposta.
fonte
Foo.create
comando bloquear por 5 minutos, o Timeout não o matará. Isso é incompatível com o exemplo fornecido na sua pergunta. ASELECT ... FOR UPDATE
poderia bloquear facilmente por longos períodos de tempo, como qualquer outra consulta.ActiveRecord::Base#find_by_sql
: gist.github.com/856100 Novamente, isso ocorre porque o AR usa bloqueio de E / S.timeout
método reverterá a transação, mas não até que o banco de dados MySQL responda a uma consulta. Portanto, otimeout
método é adequado para evitar transações longas no lado do cliente ou transações longas que consistem em várias consultas relativamente curtas, mas não para transações longas nas quais uma única consulta é a culpada.