Impostazione di un limite di tempo per una transazione in MySQL / InnoDB


11

Ciò è scaturito da questa domanda correlata , in cui volevo sapere come forzare due transazioni in modo sequenziale in un caso banale (in cui entrambi operano su una sola riga). Ho ricevuto una risposta, utilizzare SELECT ... FOR UPDATEcome prima riga di entrambe le transazioni, ma ciò porta a un problema: se la prima transazione non viene mai impegnata o ripristinata, la seconda transazione verrà bloccata indefinitamente. La innodb_lock_wait_timeoutvariabile imposta il numero di secondi dopo i quali al client che tenta di effettuare la seconda transazione verrà detto "Scusa, riprova" ... ma per quanto ne so, ci riproverebbero fino al riavvio del server successivo. Così:

  1. Sicuramente ci deve essere un modo per forzare un ROLLBACKse una transazione sta prendendo per sempre? Devo ricorrere all'uso di un demone per uccidere tali transazioni e, in tal caso, come sarebbe un demone simile?
  2. Se una connessione viene interrotta da wait_timeouto durante la interactive_timeouttransazione, viene ripristinata la transazione? C'è un modo per testarlo dalla console?

Chiarimento : innodb_lock_wait_timeoutimposta il numero di secondi in cui una transazione attenderà il rilascio di un blocco prima di rinunciare; quello che voglio è un modo per forzare il rilascio di un lucchetto.

Aggiornamento 1 : ecco un semplice esempio che dimostra perché innodb_lock_wait_timeoutnon è sufficiente per garantire che la seconda transazione non sia bloccata dalla prima:

START TRANSACTION;
SELECT SLEEP(55);
COMMIT;

Con l'impostazione predefinita di innodb_lock_wait_timeout = 50, questa transazione si completa senza errori dopo 55 secondi. E se aggiungi una UPDATEprima della SLEEPriga, quindi avvia una seconda transazione da un altro client che tenta SELECT ... FOR UPDATEla stessa riga, è la seconda transazione a scadere, non quella che si è addormentata.

Quello che sto cercando è un modo per forzare la fine del sonno riposante di questa transazione.

Aggiornamento 2 : in risposta alle preoccupazioni di hobodave su quanto sia realistico l'esempio sopra, ecco uno scenario alternativo: un DBA si collega a un server live ed esegue

START TRANSACTION
SELECT ... FOR UPDATE

dove la seconda riga blocca una riga in cui l'applicazione scrive frequentemente. Quindi il DBA viene interrotto e si allontana, dimenticando di terminare la transazione. L'applicazione si ferma fino a quando la riga non viene sbloccata. Vorrei ridurre al minimo il tempo in cui l'applicazione è bloccata a causa di questo errore.


Hai appena aggiornato la tua domanda per renderla completamente in conflitto con la tua domanda iniziale. Si afferma in grassetto "Se la prima transazione non viene mai eseguita il commit o il rollback, la seconda transazione verrà bloccata indefinitamente." Lo confondi con il tuo ultimo aggiornamento: "è la seconda transazione che scade, non quella che si è addormentata". -- sul serio?!
hobodave

Suppongo che il mio fraseggio avrebbe potuto essere più chiaro. Con "bloccato indefinitamente", intendevo che la seconda transazione sarebbe scaduta con un messaggio di errore che diceva "prova a riavviare la transazione"; ma farlo sarebbe inutile, perché sarebbe appena uscito di nuovo. Quindi la seconda transazione è bloccata, non verrà mai completata, poiché continuerà a scadere.
Trevor Burnham,

@Trevor: il tuo fraseggio era perfettamente chiaro. Hai semplicemente aggiornato la tua domanda per porre una cosa completamente diversa, che è una forma estremamente negativa.
hobodave

La domanda è sempre stata la stessa: è un problema che la seconda transazione venga bloccata indefinitamente quando la prima transazione non viene mai impegnata o ripristinata. Voglio forzare a ROLLBACKsulla prima transazione se il completamento richiede più di nsecondi. C'è un modo per farlo?
Trevor Burnham,

1
@TrevorBurnham Mi chiedo anche perché non MYSQLabbia una configurazione per prevenire questo scenario. Perché non è accettabile il blocco del server a causa dell'irresponsabilità dei client. Non ho trovato alcuna difficoltà a capire la tua domanda, anche se è così rilevante.
Dinoop paloli,

Risposte:



3

Poiché la tua domanda viene posta qui su ServerFault, è logico supporre che tu stia cercando una soluzione MySQL a un problema MySQL, in particolare nel regno delle conoscenze in cui un amministratore di sistema e / o un DBA avrebbero esperienza. Come tale, i seguenti la sezione risponde alle tue domande:

Se la prima transazione non viene mai sottoposta a commit o rollback, la seconda transazione verrà bloccata indefinitamente

No, non lo farà. Penso che tu non capisca innodb_lock_wait_timeout. Fa esattamente quello che ti serve.

Tornerà con un errore come indicato nel manuale:

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
  • Per definizione, questo non è indefinito . Se l'applicazione si sta ricollegando e bloccando ripetutamente, l'applicazione sta "bloccando indefinitamente", non la transazione. La seconda transazione si blocca decisamente per alcuni innodb_lock_wait_timeoutsecondi.

Per impostazione predefinita, la transazione non verrà ripristinata. È responsabilità del codice dell'applicazione decidere come gestire questo errore, se si sta riprovando o eseguendo il rollback.

Se si desidera il rollback automatico, questo è spiegato anche nel manuale:

La transazione corrente non viene ripristinata. (Per ripristinare l'intera transazione, avviare il server con l' --innodb_rollback_on_timeoutopzione.


RE: i tuoi numerosi aggiornamenti e commenti

Innanzitutto, hai dichiarato nei tuoi commenti che "intendevi" dire che vuoi un modo per timeout della prima transazione che sta bloccando indefinitamente. Ciò non risulta dalla domanda originale e è in conflitto con "Se la prima transazione non viene mai eseguita o ripristinata, la seconda transazione verrà bloccata indefinitamente".

Tuttavia, posso rispondere anche a questa domanda. Il protocollo MySQL non ha un "timeout di query". Ciò significa che non è possibile eseguire il timeout della prima transazione bloccata. È necessario attendere fino al termine o interrompere la sessione. Quando la sessione viene interrotta, il server ripristinerà automaticamente la transazione.

L'unica altra alternativa sarebbe quella di utilizzare o scrivere una libreria mysql che utilizza I / O non bloccanti che consentirebbe all'applicazione di terminare il thread / fork eseguendo la query dopo N secondi. L'implementazione e l'utilizzo di tale libreria vanno oltre lo scopo di ServerFault. Questa è una domanda appropriata per StackOverflow .

In secondo luogo, hai dichiarato quanto segue nei tuoi commenti:

In realtà ero più interessato alla mia domanda con uno scenario in cui l'app client si blocca (diciamo, viene catturata in un ciclo infinito) nel corso di una transazione rispetto a quella in cui la transazione impiega molto tempo alla fine di MySQL.

Questo non era affatto evidente nella tua domanda originale, e ancora non lo è. Questo può essere individuato solo dopo aver condiviso questa notizia piuttosto importante nel commento.

Se questo è effettivamente il problema che stai cercando di risolvere, temo che tu l'abbia chiesto nel forum sbagliato. È stato descritto un problema di programmazione a livello di applicazione che richiede una soluzione di programmazione, che MySQL non è in grado di fornire ed è al di fuori dell'ambito di questa comunità. La tua ultima risposta risponde alla domanda "Come posso impedire a un programma Ruby di eseguire un ciclo continuo?". Questa domanda è fuori tema per questa comunità e dovrebbe essere posta su StackOverflow .


Ci dispiace, ma mentre è vero quando la transazione è in attesa di un blocco (come il nome e la documentazione di innodb_lock_wait_timeoutsuggerire), non è così nella situazione che ho posto: dove il client si blocca o si arresta in modo anomalo prima che ottenga la modifica per fare un COMMITo ROLLBACK.
Trevor Burnham,

@Trevor: hai semplicemente torto. Questo è banale da testare da parte tua. L'ho appena testato da parte mia. Per favore, riprendi il tuo voto negativo.
hobodave

@hobo, solo perché la tua destra non significa che gli debba piacere.
Chris S

Chris, potresti spiegarmi come ha ragione? Come avverrà la seconda operazione, se non COMMITo ROLLBACKmessaggio viene inviato per risolvere la prima transazione? Hobodave, forse sarebbe utile se tu presentassi il tuo codice di prova.
Trevor Burnham,

2
@Trevor: non hai un senso. Vuoi serializzare le transazioni, giusto? Sì, l'hai detto nell'altra domanda e ripeterlo qui in questa domanda. Se la prima transazione non è stata completata, come ci si aspetterebbe che la seconda transazione si completasse? Hai esplicitamente detto di attendere il rilascio del blocco su quella riga. Pertanto, deve aspettare. Cosa non capisci al riguardo?
hobodave

1

In una situazione simile il mio team ha deciso di utilizzare pt-kill ( https://www.percona.com/doc/percona-toolkit/2.1/pt-kill.html ). Può segnalare o eliminare query che (tra le altre cose) sono in esecuzione da troppo tempo. È quindi possibile eseguirlo in cron ogni X minuti.

Il problema era che una query che aveva un tempo di esecuzione inaspettatamente lungo (ad es. Indefinito) bloccava una procedura di backup che cercava di svuotare le tabelle con il blocco di lettura, che a sua volta bloccava tutte le altre query sull'attesa di svuotamento delle tabelle che non scadevano mai perché non stanno aspettando un blocco, che ha comportato senza errori nell'applicazione, che alla fine ha comportato senza avvisi agli amministratori e l'arresto dell'applicazione.

La soluzione era ovviamente quella di correggere la query iniziale, ma per evitare che la situazione si verificasse accidentalmente in futuro, sarebbe bello se fosse possibile uccidere automaticamente (o riferire) transazioni / query che durano più a lungo di un tempo specifico, quale autore della domanda sembra porsi. Questo è fattibile con pt-kill.


0

Una soluzione semplice sarà quella di disporre di una procedura memorizzata per eliminare le query che richiedono più tempo del timeouttempo richiesto e utilizzare l' innodb_rollback_on_timeoutopzione insieme ad essa.


-2

Dopo aver cercato su Google per un po ', sembra che non ci sia un modo diretto per impostare un limite di tempo per transazione. Ecco la migliore soluzione che sono riuscito a trovare:

Il comando

SHOW PROCESSLIST;

ti dà una tabella con tutti i comandi attualmente in esecuzione e il tempo (in secondi) da quando sono stati avviati. Quando un client ha smesso di dare comandi, viene descritto come dare un Sleepcomando, con la Timecolonna che indica il numero di secondi dall'ultimo comando completato. Quindi puoi accedere manualmente e KILLtutto ciò che ha un Timevalore superiore, diciamo, a 5 secondi se sei sicuro che nessuna query dovrebbe richiedere più di 5 secondi sul tuo database. Ad esempio, se il processo con ID 3 ha un valore di 12 nella sua Timecolonna, è possibile farlo

KILL 3;

La documentazione per la sintassi di KILL suggerisce che una transazione eseguita dal thread con quell'ID verrebbe ripristinata (anche se non l'ho testata, quindi fai attenzione).

Ma come automatizzare questo e uccidere tutti gli script degli straordinari ogni 5 secondi? Il primo commento su quella stessa pagina ci dà un suggerimento, ma il codice è in PHP; sembra che non siamo in grado di fare una SELECTsul SHOW PROCESSLISTdall'interno del client MySQL. Tuttavia, supponendo che abbiamo un demone PHP che eseguiamo ogni $MAX_TIMEsecondo, potrebbe assomigliare a questo:

$result = mysql_query("SHOW FULL PROCESSLIST");
while ($row=mysql_fetch_array($result)) {
  $process_id=$row["Id"];
    if ($row["Time"] > $MAX_TIME) {
      $sql="KILL $process_id";
      mysql_query($sql);
  }
}

Si noti che ciò avrebbe l'effetto collaterale di forzare la riconnessione di tutte le connessioni client inattive, non solo quelle che si comportano male.

Se qualcuno ha una risposta migliore, mi piacerebbe ascoltarla.

Modifica: esiste almeno un serio rischio di utilizzo KILLin questo modo. Supponiamo di utilizzare lo script sopra per KILLogni connessione inattiva per 10 secondi. Dopo che una connessione è rimasta inattiva per 10 secondi, ma prima che KILLvenga emessa, l'utente la cui connessione viene interrotta emette un START TRANSACTIONcomando. Vengono quindi KILLed. Mandano un UPDATEe poi, ripensandoci due volte, emettono un ROLLBACK. Tuttavia, poiché KILLha eseguito il rollback della transazione originale, l'aggiornamento è stato eseguito immediatamente e non è possibile eseguire il rollback! Vedi questo post per un caso di test.


2
Questo commento è destinato ai futuri lettori di questa risposta. Per favore, non farlo, anche se è la risposta accettata.
hobodave

OK, piuttosto che effettuare il downvoting e lasciare un commento malizioso, che ne dici di spiegare perché questa sarebbe una cattiva idea? La persona che ha pubblicato lo script PHP originale ha detto che ha impedito il blocco del server; se c'è un modo migliore per raggiungere lo stesso obiettivo, mostramelo.
Trevor Burnham,

6
Il commento non è snide. È un avvertimento per tutti i futuri lettori di non tentare di utilizzare la "soluzione". Uccidere automaticamente le transazioni a lungo termine è un'idea unilateralmente terribile. Non dovrebbe mai essere fatto . Questa è la spiegazione. Le sessioni di uccisione dovrebbero essere un'eccezione, non una norma. La causa principale del problema inventato è l'errore dell'utente. Non si creano soluzioni tecniche per i DBA che bloccano le file e quindi "camminano via". Li licenzi.
Hobodave

-2

Come suggerito da hobodave in un commento, in alcuni client (anche se apparentemente non l' mysqlutilità della riga di comando) è possibile impostare un limite di tempo per una transazione. Ecco una dimostrazione usando ActiveRecord in Ruby:

require 'rubygems'
require 'timeout'
require 'active_record'

Timeout::timeout(5) {
  Foo.transaction do
    Foo.create(:name => 'Bar')
    sleep 10
  end
}

In questo esempio, la transazione scade dopo 5 secondi e viene automaticamente ripristinata . ( Aggiorna in risposta al commento di hobodave: se il database impiega più di 5 secondi per rispondere, la transazione verrà ripristinata non appena lo farà, non appena.) Se desideri assicurarti che tutte le tue transazioni scadano dopo npochi secondi, potresti creare un wrapper attorno ad ActiveRecord. Immagino che questo si applichi anche alle librerie più popolari in Java, .NET, Python, ecc., Ma non le ho ancora testate. (Se hai, per favore pubblica un commento su questa risposta.)

Le transazioni in ActiveRecord hanno anche il vantaggio di essere sicure se KILLviene emesso un, a differenza delle transazioni eseguite dalla riga di comando. Vedi /dba/1561/mysql-client-believes-theyre-in-a-transaction-gets-killed-wreaks-havoc .

Non sembra possibile imporre un tempo di transazione massimo sul lato server, tranne che attraverso uno script come quello che ho pubblicato nell'altra mia risposta.


Si è trascurato che ActiveRecord utilizza I / O sincrono (blocco). Tutto quello che lo script sta facendo è interrompere il comando Ruby sleep. Se il Foo.createcomando viene bloccato per 5 minuti, il timeout non lo ucciderebbe. Questo è incompatibile con l'esempio fornito nella tua domanda. A SELECT ... FOR UPDATEpotrebbe facilmente bloccarsi per lunghi periodi di tempo, come qualsiasi altra query.
hobodave

Va notato che quasi tutte le librerie client mysql utilizzano l'I / O di blocco, quindi il suggerimento nella mia risposta che sarebbe necessario utilizzare o scrivere il proprio che utilizzava l'I / O non bloccante. Ecco una semplice dimostrazione del Timeout inutile: gist.github.com/856100
hobodave

In realtà ero più interessato alla mia domanda con uno scenario in cui l'app client si blocca (diciamo, viene catturata in un ciclo infinito) nel corso di una transazione rispetto a quella in cui la transazione impiega molto tempo alla fine di MySQL. Ma grazie, è un buon punto. Ho aggiornato la domanda per annotarla.)
Trevor Burnham

Non posso duplicare i risultati rivendicati. Ho aggiornato l' essenziale da utilizzare ActiveRecord::Base#find_by_sql: gist.github.com/856100 Ancora una volta, perché AR utilizza I / O di blocco.
Hobodave

@hobodave Sì, quella modifica era errata ed è stata corretta. Per essere chiari: il timeoutmetodo eseguirà il rollback della transazione, ma non fino a quando il database MySQL non risponderà a una query. Quindi il timeoutmetodo è adatto per prevenire transazioni lunghe sul lato client o transazioni lunghe che consistono in diverse query relativamente brevi, ma non per transazioni lunghe in cui una singola query è colpevole.
Trevor Burnham,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.