Come eseguire il debug del timeout di attesa di blocco superato su MySQL?


269

Nei miei log degli errori di produzione ogni tanto vedo:

SQLSTATE [HY000]: errore generale: 1205 superamento del timeout di attesa blocco; prova a riavviare la transazione

So quale query sta provando ad accedere al database in quel momento, ma c'è un modo per scoprire quale query ha avuto il blocco in quel preciso momento?


1
Consiglio vivamente a tutti di dare una risposta alla risposta di Eirik
kommradHomer,

Risposte:


261

Ciò che dà via è la parola transazione . È evidente dall'affermazione che la query stava tentando di cambiare almeno una riga in una o più tabelle InnoDB.

Poiché conosci la query, tutti i tavoli a cui si accede sono candidati per essere il colpevole.

Da lì, dovresti essere in grado di correre SHOW ENGINE INNODB STATUS\G

Dovresti essere in grado di vedere le tabelle interessate

Ottieni tutti i tipi di informazioni aggiuntive su blocco e mutex.

Ecco un esempio da uno dei miei clienti:

mysql> show engine innodb status\G
*************************** 1. row ***************************
  Type: InnoDB
  Name:
Status:
=====================================
110514 19:44:14 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 4 seconds
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 9014315, signal count 7805377
Mutex spin waits 0, rounds 11487096053, OS waits 7756855
RW-shared spins 722142, OS waits 211221; RW-excl spins 787046, OS waits 39353
------------------------
LATEST FOREIGN KEY ERROR
------------------------
110507 21:41:35 Transaction:
TRANSACTION 0 606162814, ACTIVE 0 sec, process no 29956, OS thread id 1223895360 updating or deleting, thread declared inside InnoDB 499
mysql tables in use 1, locked 1
14 lock struct(s), heap size 3024, 8 row lock(s), undo log entries 1
MySQL thread id 3686635, query id 124164167 10.64.89.145 viget updating
DELETE FROM file WHERE file_id in ('6dbafa39-7f00-0001-51f2-412a450be5cc' )
Foreign key constraint fails for table `backoffice`.`attachment`:
,
  CONSTRAINT `attachment_ibfk_2` FOREIGN KEY (`file_id`) REFERENCES `file` (`file_id`)
Trying to delete or update in parent table, in index `PRIMARY` tuple:
DATA TUPLE: 17 fields;
 0: len 36; hex 36646261666133392d376630302d303030312d353166322d343132613435306265356363; asc 6dbafa39-7f00-0001-51f2-412a450be5cc;; 1: len 6; hex 000024214f7e; asc   $!O~;; 2: len 7; hex 000000400217bc; asc    @   ;; 3: len 2; hex 03e9; asc   ;; 4: len 2; hex 03e8; asc   ;; 5: len 36; hex 65666635323863622d376630302d303030312d336632662d353239626433653361333032; asc eff528cb-7f00-0001-3f2f-529bd3e3a302;; 6: len 40; hex 36646234376337652d376630302d303030312d353166322d3431326132346664656366352e6d7033; asc 6db47c7e-7f00-0001-51f2-412a24fdecf5.mp3;; 7: len 21; hex 416e67656c73204e6f7720436f6e666572656e6365; asc Angels Now Conference;; 8: len 34; hex 416e67656c73204e6f7720436f6e666572656e6365204a756c7920392c2032303131; asc Angels Now Conference July 9, 2011;; 9: len 1; hex 80; asc  ;; 10: len 8; hex 8000124a5262bdf4; asc    JRb  ;; 11: len 8; hex 8000124a57669dc3; asc    JWf  ;; 12: SQL NULL; 13: len 5; hex 8000012200; asc    " ;; 14: len 1; hex 80; asc  ;; 15: len 2; hex 83e8; asc   ;; 16: len 4; hex 8000000a; asc     ;;

But in child table `backoffice`.`attachment`, in index `PRIMARY`, there is a record:
PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 30; hex 36646261666133392d376630302d303030312d353166322d343132613435; asc 6dbafa39-7f00-0001-51f2-412a45;...(truncated); 1: len 30; hex 38666164663561652d376630302d303030312d326436612d636164326361; asc 8fadf5ae-7f00-0001-2d6a-cad2ca;...(truncated); 2: len 6; hex 00002297b3ff; asc   "   ;; 3: len 7; hex 80000040070110; asc    @   ;; 4: len 2; hex 0000; asc   ;; 5: len 30; hex 416e67656c73204e6f7720436f6e666572656e636520446f63756d656e74; asc Angels Now Conference Document;;

------------
TRANSACTIONS
------------
Trx id counter 0 620783814
Purge done for trx's n:o < 0 620783800 undo n:o < 0 0
History list length 35
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 0 0, not started, process no 29956, OS thread id 1192212800
MySQL thread id 5341758, query id 189708501 127.0.0.1 lwdba
show innodb status
---TRANSACTION 0 620783788, not started, process no 29956, OS thread id 1196472640
MySQL thread id 5341773, query id 189708353 10.64.89.143 viget
---TRANSACTION 0 0, not started, process no 29956, OS thread id 1223895360
MySQL thread id 5341667, query id 189706152 10.64.89.145 viget
---TRANSACTION 0 0, not started, process no 29956, OS thread id 1227888960
MySQL thread id 5341556, query id 189699857 172.16.135.63 lwdba
---TRANSACTION 0 620781112, not started, process no 29956, OS thread id 1222297920
MySQL thread id 5341511, query id 189696265 10.64.89.143 viget
---TRANSACTION 0 620783736, not started, process no 29956, OS thread id 1229752640
MySQL thread id 5339005, query id 189707998 10.64.89.144 viget
---TRANSACTION 0 620783785, not started, process no 29956, OS thread id 1198602560
MySQL thread id 5337583, query id 189708349 10.64.89.145 viget
---TRANSACTION 0 620783469, not started, process no 29956, OS thread id 1224161600
MySQL thread id 5333500, query id 189708478 10.64.89.144 viget
---TRANSACTION 0 620781240, not started, process no 29956, OS thread id 1198336320
MySQL thread id 5324256, query id 189708493 10.64.89.145 viget
---TRANSACTION 0 617458223, not started, process no 29956, OS thread id 1195141440
MySQL thread id 736, query id 175038790 Has read all relay log; waiting for the slave I/O thread to update it
--------
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (write thread)
Pending normal aio reads: 0, aio writes: 0,
 ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0
Pending flushes (fsync) log: 0; buffer pool: 0
519878 OS file reads, 18962880 OS file writes, 13349046 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 6.25 writes/s, 4.50 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 1190, seg size 1192,
174800 inserts, 174800 merged recs, 54439 merges
Hash table size 35401603, node heap has 35160 buffer(s)
0.50 hash searches/s, 11.75 non-hash searches/s
---
LOG
---
Log sequence number 28 1235093534
Log flushed up to   28 1235093534
Last checkpoint at  28 1235091275
0 pending log writes, 0 pending chkp writes
12262564 log i/o's done, 3.25 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 18909316674; in additional pool allocated 1048576
Dictionary memory allocated 2019632
Buffer pool size   1048576
Free buffers       175763
Database pages     837653
Modified db pages  6
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages read 770138, created 108485, written 7795318
0.00 reads/s, 0.00 creates/s, 4.25 writes/s
Buffer pool hit rate 1000 / 1000
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
1 read views open inside InnoDB
Main thread process no. 29956, id 1185823040, state: sleeping
Number of rows inserted 6453767, updated 4602534, deleted 3638793, read 388349505551
0.25 inserts/s, 1.25 updates/s, 0.00 deletes/s, 2.75 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================

1 row in set, 1 warning (0.00 sec)

Dovresti considerare di aumentare il valore di timeout di attesa del blocco per InnoDB impostando innodb_lock_wait_timeout , il valore predefinito è 50 sec

mysql> show variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50    |
+--------------------------+-------+
1 row in set (0.01 sec)

Puoi impostarlo su un valore più alto in modo /etc/my.cnfpermanente con questa linea

[mysqld]
innodb_lock_wait_timeout=120

e riavvia mysql. Se non riesci a riavviare mysql in questo momento, esegui questo:

SET GLOBAL innodb_lock_wait_timeout = 120; 

Puoi anche impostarlo per la durata della sessione

SET innodb_lock_wait_timeout = 120; 

seguito dalla tua richiesta


5
Per InnoDB integrato, la innodb_lock_wait_timeoutvariabile può essere impostata solo all'avvio del server. Per il plug-in InnoDB, può essere impostato all'avvio o modificato in fase di esecuzione e ha valori sia globali che di sessione.
Timo Huovinen,

1
Ciao @rolandomysqldba, potete per favore darmi suggerimento sul mio questo post: stackoverflow.com/questions/18267565/...
Manish Sapkal

2
Ottengo questo errore quando provo a eseguire la prima query:SQL Error (1064): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\G' at line 1
Iulian Onofrei

1
@Pacerier Ogni volta che mysqld viene riavviato, è necessario eseguire SET GLOBAL innodb_lock_wait_timeout = 120;nuovamente. Se /etc/my.cnfha l'opzione, innodb_lock_wait_timeoutè impostato per te. Non tutti hanno il privilegio SUPER di cambiarlo globalmente per tutti gli altri ( dev.mysql.com/doc/refman/5.6/en/… )
RolandoMySQLDBA

3
@IulianOnofrei il carattere \ G è una caratteristica speciale della riga di comando di MySQL e cambia la modalità di visualizzazione dell'output. Per altri client MySQL basta usare un punto e virgola normale.
thenickdude,

83

Come qualcuno ha menzionato in uno dei tanti thread SO riguardanti questo problema: a volte il processo che ha bloccato la tabella appare come inattivo nella lista dei processi! Mi stavo strappando i capelli fino a quando non ho ucciso tutti i thread dormienti che erano aperti nel database in questione (nessuno era attivo al momento). Ciò ha finalmente sbloccato la tabella e lasciato eseguire la query di aggiornamento.

Il commentatore ha detto qualcosa di simile a "A volte un thread MySQL blocca una tabella, quindi dorme mentre attende che accada qualcosa di non correlato a MySQL".

Dopo aver riesaminato nuovamente il show engine innodb statusregistro (dopo aver rintracciato il client responsabile del blocco), ho notato che il thread bloccato in questione era elencato in fondo all'elenco delle transazioni, sotto le query attive che stavano per errori fuori a causa del blocco congelato:

------------------
---TRANSACTION 2744943820, ACTIVE 1154 sec(!!)
2 lock struct(s), heap size 376, 2 row lock(s), undo log entries 1
MySQL thread id 276558, OS thread handle 0x7f93762e7710, query id 59264109 [ip] [database] cleaning up
Trx read view will not see trx with id >= 2744943821, sees < 2744943821

(non sono sicuro se il messaggio "Visualizzazione lettura Trx" è correlato al blocco bloccato, ma a differenza delle altre transazioni attive, questo non viene visualizzato con la query che è stata emessa e invece afferma che la transazione sta "ripulendo", ma ha più serrature di riga)

La morale della storia è che una transazione può essere attiva anche se il thread sta dormendo.


2
non posso dire che mi hai salvato la vita, ma di sicuro mi hai messo in pace. Leggendo la tua risposta, ho trovato un thread inquietante che è attivo per 3260 secondi e non si presenta da nessuna parte. dopo averlo ucciso, tutti i miei problemi sono stati risolti!
kommradHomer il

Questo era il mio problema Una transazione in sospeso con un tempo di 20.000 secondi che impediva il corretto funzionamento di Lavori ritardati in un'app Rails. Grazie @Eirik
bigtex777,

Qualche idea sul perché una transazione in sospeso non venga uccisa comunque? Ad esempio, è possibile impostare un timeout entro il quale una transazione deve concludersi?
patrickdavey,

1
Altri comandi che potrebbero essere utili nella ricerca di transazioni bloccate: show processlist;mostrare un elenco esaustivo dei processi attualmente in esecuzione, il che è carino perché è una versione ridotta di show engine innodb status\g. Inoltre, se il tuo db si trova su un'istanza di Amazon RDS, puoi usarlo CALL mysql.rds_kill(<thread_id>);per uccidere i thread. Esso dispone di autorizzazioni più elevate credo, perché mi ha permesso di uccidere più processi di pianura kill <thread_id>;- nota questi dovrebbero essere eseguiti entro MySQL CLI
nickang

1
Qualcuno ha una fonte per questo - forse una pagina di documentazione che indica i blocchi sono posizionati prima della fase COMMIT? Non sono riuscito a trovare nulla, nonostante abbia visto questo problema esatto ed è stato chiarito uccidendo il thread dormiente che conteneva le serrature.
Erin Schoonover,

42

A causa della popolarità di MySQL, non c'è da stupirsi che il timeout di attesa di Lock sia stato superato; provare a riavviare l' eccezione della transazione ottiene così tanta attenzione su SO.

Maggiore è la contesa, maggiore è la possibilità di deadlock, che un motore DB risolverà eseguendo il timeout di una delle transazioni deadlock. Inoltre, le transazioni a lungo termine che hanno modificato (ad esempio UPDATEo DELETE) un numero elevato di voci (che richiedono blocchi per evitare anomalie di scrittura sporca come spiegato nel libro Persistenza Java ad alte prestazioni ) hanno maggiori probabilità di generare conflitti con altre transazioni.

Sebbene InnoDB MVCC, è ancora possibile richiedere blocchi espliciti utilizzando la FOR UPDATEclausola . Tuttavia, a differenza di altri DB popolari (Oracle, MSSQL, PostgreSQL, DB2), MySQL utilizza REPEATABLE_READcome livello di isolamento predefinito .

Ora, i blocchi acquisiti (modificando le righe o utilizzando il blocco esplicito) vengono mantenuti per la durata della transazione attualmente in esecuzione. Se vuoi una buona spiegazione della differenza tra REPEATABLE_READe READ COMMITTEDriguardo al blocco, leggi questo articolo di Percona .

In REPEATABLE READ ogni blocco acquisito durante una transazione viene mantenuto per la durata della transazione.

In READ COMMITTED i blocchi che non corrispondono alla scansione vengono rilasciati dopo il completamento della STATEMENT.

...

Ciò significa che in READ COMMITTED altre transazioni sono libere di aggiornare le righe che non sarebbero state in grado di aggiornare (in REPEATABLE READ) una volta completata l'istruzione UPDATE.

Pertanto: più restrittivo è il livello di isolamento ( REPEATABLE_READ, SERIALIZABLE) maggiore è la possibilità di deadlock. Questo non è un problema "di per sé", è un compromesso.

Puoi ottenere ottimi risultati con READ_COMMITED, poiché hai bisogno di una prevenzione degli aggiornamenti persi a livello di applicazione quando utilizzi transazioni logiche che si estendono su più richieste HTTP. L' approccio di blocco ottimistico prende di mira gli aggiornamenti persi che potrebbero verificarsi anche se si utilizza il SERIALIZABLElivello di isolamento riducendo al contempo il conflitto di blocco consentendoti di utilizzarlo READ_COMMITED.


4
Il timeout di attesa del blocco non è diverso dal deadlock? Ad esempio, se un thread mantiene un blocco per 60 secondi per motivi legittimi, potrebbe verificarsi il timeout di attesa del blocco. Non è vero che se esiste davvero un deadlock, MySQL lo rileverà e ucciderà immediatamente una transazione e ciò non è correlato al timeout di attesa del blocco?
ColinM,

1
Hai ragione. Il DB rileva il dead-lock dopo il timeout e termina un processo in attesa, quindi una transazione vince mentre l'altra fallisce. Ma più a lungo si tiene un lucchetto, meno scalabile diventa l'applicazione. Anche se non ci si imbatte in dead-lock, si aumenterà comunque la parte serializzabile del comportamento di runtime dell'applicazione.
Vlad Mihalcea,

19

Per la cronaca, l'eccezione del timeout di attesa del blocco si verifica anche quando è presente un deadlock e MySQL non è in grado di rilevarlo, quindi scade il timeout. Un altro motivo potrebbe essere una query estremamente lunga, che è più facile da risolvere / riparare, tuttavia, e non descriverò questo caso qui.

MySQL è in genere in grado di gestire i deadlock se sono costruiti "correttamente" all'interno di due transazioni. MySQL quindi uccide / esegue il rollback di una transazione che possiede meno blocchi (è meno importante in quanto avrà un impatto su meno righe) e lascia terminare l'altra.

Supponiamo ora che ci siano due processi A e B e 3 transazioni:

Process A Transaction 1: Locks X
Process B Transaction 2: Locks Y
Process A Transaction 3: Needs Y => Waits for Y
Process B Transaction 2: Needs X => Waits for X
Process A Transaction 1: Waits for Transaction 3 to finish

(see the last two paragraph below to specify the terms in more detail)

=> deadlock 

Questa è una configurazione molto sfortunata perché MySQL non riesce a vedere che c'è un deadlock (suddiviso in 3 transazioni). Quindi quello che fa MySQL è ... niente! Aspetta solo, dal momento che non sa cosa fare. Aspetta che il primo blocco acquisito superi il timeout (Processo A Transazione 1: Blocca X), quindi questo sbloccherà il Blocco X, che sblocca la Transazione 2 ecc.

L'arte è scoprire cosa (quale query) provoca il primo blocco (Blocco X). Sarai in grado di vedere facilmente ( show engine innodb status) che la Transazione 3 attende la Transazione 2, ma non vedrai quale Transazione 2 è in attesa (Transazione 1). MySQL non stamperà alcun blocco o query associati alla Transazione 1. L'unico suggerimento sarà che in fondo all'elenco delle transazioni (della show engine innodb statusstampa), apparentemente la Transazione 1 non farà nulla (ma in effetti aspettando che Transazione 3 finire).

La tecnica per trovare quale query SQL fa sì che il blocco (Lock X) venga concesso per una determinata transazione in attesa è descritta qui Tracking MySQL query history in long running transactions

Se ti stai chiedendo quale sia esattamente il processo e la transazione nell'esempio. Il processo è un processo PHP. La transazione è una transazione definita da innodb-trx-table . Nel mio caso, ho avuto due processi PHP, in ognuno ho avviato manualmente una transazione. La parte interessante è che anche se ho avviato una transazione in un processo, MySQL ha utilizzato internamente due transazioni separate (non ho idea del perché, forse alcuni sviluppatori MySQL possono spiegare).

MySQL sta gestendo le proprie transazioni internamente e ha deciso (nel mio caso) di utilizzare due transazioni per gestire tutte le richieste SQL provenienti dal processo PHP (processo A). L'affermazione che Transaction 1 sta aspettando che Transaction 3 finisca è una cosa interna di MySQL. MySQL "sapeva" che la Transazione 1 e la Transazione 3 erano effettivamente istanziate come parte di una richiesta di "transazione" (dal processo A). Ora l'intera "transazione" è stata bloccata perché è stata bloccata la Transazione 3 (una sottoparte della "transazione"). Poiché "transazione" non è stata in grado di completare la transazione 1 (anche una sottoparte della "transazione") è stata contrassegnata come non completata. Questo è ciò che intendevo per "Transazione 1 attende che la Transazione 3 finisca".


14

Il grosso problema con questa eccezione è che di solito non è riproducibile in un ambiente di test e non siamo in giro per eseguire lo stato del motore innodb quando si verifica su prod. Quindi in uno dei progetti ho inserito il seguente codice in un blocco catch per questa eccezione. Ciò mi ha aiutato a rilevare lo stato del motore quando si è verificata l'eccezione. Questo ha aiutato molto.

Statement st = con.createStatement();
ResultSet rs =  st.executeQuery("SHOW ENGINE INNODB STATUS");
while(rs.next()){
    log.info(rs.getString(1));
    log.info(rs.getString(2));
    log.info(rs.getString(3));
}

11

Dai un'occhiata alla pagina man pt-deadlock-loggerdell'utilità :

brew install percona-toolkit
pt-deadlock-logger --ask-pass server_name

Estrae le informazioni da quelle engine innodb statussopra menzionate e può anche essere utilizzato per creare un oggetto daemonche viene eseguito ogni 30 secondi.


3
questo strumento fa ora parte del toolkit Percona
Brad Mace,

I timeout di attesa del blocco non sono gli stessi dei deadlock, in particolare innodb non mostra alcuna informazione su di essi perché non vengono rilevati deadlock, quindi non credo che pt-deadlock-logger sia di alcun aiuto.
Jay Paroline,

I timeout e i deadlock dei blocchi sono correlati - vedere dev.mysql.com/doc/refman/5.7/en/innodb-deadlock-detection.html
Andrei Sura

11

Estrapolando dalla risposta di Rolando sopra, sono questi che stanno bloccando la tua query:

---TRANSACTION 0 620783788, not started, process no 29956, OS thread id 1196472640
MySQL thread id 5341773, query id 189708353 10.64.89.143 viget

Se devi eseguire la tua query e non puoi aspettare che vengano eseguiti gli altri, eliminali usando l'id thread MySQL:

kill 5341773 <replace with your thread id>

(dall'interno di mysql, non della shell, ovviamente)

Devi trovare gli ID thread da:

show engine innodb status\G

comando e capire quale è quello che sta bloccando il database.


1
Come fai a saperlo 5341773? Non vedo ciò che distingue quello dagli altri.
Wodin,

No, probabilmente non è quel threadID, era un esempio. Devi trovare gli ID thread dal comando "show engine innodb status \ G" e capire quale è quello che sta bloccando il database.
Ellert van Koperen,

1
Grazie. Quindi, in altre parole, non c'è modo di dire quale sia senza, ad esempio, ucciderli uno per uno?
Wodin,

Nell'elenco delle transazioni puoi vedere quali sono in esecuzione e per quanto tempo. Quindi non c'è bisogno di ucciderli uno per uno, quella lista di solito ti dà una buona idea di cosa sta succedendo.
Ellert van Koperen,

10

Ecco cosa alla fine ho dovuto fare per capire quale "altra query" ha causato il problema di timeout del blocco. Nel codice dell'applicazione, tracciamo tutte le chiamate di database in sospeso su un thread separato dedicato a questa attività. Se una chiamata DB impiega più di N secondi (per noi sono 30 secondi) registriamo:

-- Pending InnoDB transactions
SELECT * FROM information_schema.innodb_trx ORDER BY trx_started; 

-- Optionally, log what transaction holds what locks
SELECT * FROM information_schema.innodb_locks;

Con quanto sopra, siamo stati in grado di individuare query simultanee che bloccavano le righe causando il deadlock. Nel mio caso, erano dichiarazioni similiINSERT ... SELECT che, a differenza dei semplici SELECT, bloccano le righe sottostanti. È quindi possibile riorganizzare il codice o utilizzare un diverso isolamento della transazione come lettura senza commit.

In bocca al lupo!


9

Puoi usare:

show full processlist

che elencherà tutte le connessioni in MySQL e lo stato attuale della connessione, nonché la query in esecuzione. C'è anche una variante più breve show processlist;che mostra la query troncata e le statistiche di connessione.



-2

Attiva MySQL general.log (intensivo del disco) e usa mysql_analyse_general_log.pl per estrarre transazioni di lunga durata, ad esempio con:

--min-duration = il tuo valore innodb_lock_wait_timeout

Disabilita general.log dopo quello.

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.