Come evitare mysql 'Deadlock trovato quando si cerca di ottenere il blocco; prova a riavviare la transazione "


286

Ho una tabella innoDB che registra gli utenti online. Viene aggiornato su ogni aggiornamento della pagina da parte di un utente per tenere traccia delle pagine su cui si trovano e della loro ultima data di accesso al sito. Ho quindi un cron che viene eseguito ogni 15 minuti per eliminare i vecchi record.

Ho trovato un 'deadlock' quando cercavo di bloccare; prova a riavviare la transazione 'per circa 5 minuti la scorsa notte e sembra essere durante l'esecuzione di INSERT in questa tabella. Qualcuno può suggerire come evitare questo errore?

=== MODIFICA ===

Ecco le query in esecuzione:

Prima visita al sito:

INSERT INTO onlineusers SET
ip = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3

Ad ogni aggiornamento della pagina:

UPDATE onlineusers SET
ips = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3
WHERE id = 888

Cron ogni 15 minuti:

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

Quindi conta alcuni dati per registrare alcune statistiche (es: membri online, visitatori online).


Potete fornire qualche dettaglio in più sulla struttura della tabella? Esistono indici cluster o non cluster?
Anders Abel,

13
dev.mysql.com/doc/refman/5.1/en/innodb-deadlocks.html - L'esecuzione di "show engine innodb status" fornirebbe utili strumenti diagnostici.
Martin

Non è una buona pratica fare una scrittura sincrona del database quando gli utenti passano su una pagina. il modo giusto per farlo è tenerlo in memoria come memcache o qualche coda veloce e scrivere su db con cron.
Nir

Risposte:


292

Un trucco semplice che può aiutare con la maggior parte dei deadlock è l'ordinamento delle operazioni in un ordine specifico.

Si ottiene un deadlock quando due transazioni stanno tentando di bloccare due blocchi in ordini opposti, ovvero:

  • connessione 1: chiave di blocco (1), chiave di blocco (2);
  • connessione 2: chiave di blocco (2), chiave di blocco (1);

Se entrambi funzionano contemporaneamente, la connessione 1 bloccherà la chiave (1), la connessione 2 bloccherà la chiave (2) e ciascuna connessione attenderà che l'altra rilasci la chiave -> deadlock.

Ora, se hai modificato le tue query in modo tale che le connessioni blocchino le chiavi nello stesso ordine, ovvero:

  • connessione 1: chiave di blocco (1), chiave di blocco (2);
  • connessione 2: chiave di blocco ( 1 ), chiave di blocco ( 2 );

sarà impossibile ottenere un punto morto.

Quindi questo è quello che suggerisco:

  1. Assicurati di non avere altre query che bloccano l'accesso a più di una chiave alla volta tranne l'istruzione delete. se lo fai (e sospetto che lo faccia), ordina il loro WHERE in (k1, k2, .. kn) in ordine crescente.

  2. Correggi la tua dichiarazione di eliminazione affinché funzioni in ordine crescente:

Modificare

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

Per

DELETE FROM onlineusers WHERE id IN (SELECT id FROM onlineusers
    WHERE datetime <= now() - INTERVAL 900 SECOND order by id) u;

Un'altra cosa da tenere a mente è che la documentazione mysql suggerisce che in caso di deadlock il client dovrebbe riprovare automaticamente. puoi aggiungere questa logica al tuo codice client. (Diciamo, 3 tentativi su questo particolare errore prima di arrendersi).


2
Se ho Transaction (autocommit = false), viene generata un'eccezione deadlock. È sufficiente riprovare la stessa istruzione.executeUpdate () o l'intera transazione è ora compressa e dovrebbe essere ripristinata + rieseguire tutto ciò che era in esecuzione in essa?
Chi il

5
se hai abilitato le transazioni, è tutto o niente. se hai avuto un'eccezione di qualsiasi tipo, è garantito che l'intera transazione non ha avuto effetto. in tal caso vorresti riavviare il tutto.
Omry Yadan,

4
Un'eliminazione basata su una selezione su una tabella enorme è molto più lenta di una semplice eliminazione
Thermech,

3
Grazie mille, amico. Il suggerimento "ordinamenti" ha risolto i miei problemi di dead lock.
Miere,

4
@OmryYadan Da quello che so, in MySQL non è possibile selezionare in una subquery dalla stessa tabella in cui si sta effettuando l'aggiornamento. dev.mysql.com/doc/refman/5.7/en/update.html
artaxerxe

73

Il deadlock si verifica quando due transazioni si attendono l'una sull'altra per acquisire un lock. Esempio:

  • Tx 1: blocco A, quindi B
  • Tx 2: blocco B, quindi A

Esistono numerose domande e risposte sui deadlock. Ogni volta che si inserisce / aggiorna / o si elimina una riga, viene acquisito un blocco. Per evitare deadlock, è necessario assicurarsi che le transazioni simultanee non aggiornino la riga in un ordine che potrebbe provocare un deadlock. In generale, prova ad acquisire il blocco sempre nello stesso ordine anche in una transazione diversa (ad esempio, sempre prima la tabella A, quindi la tabella B).

Un altro motivo di deadlock nel database può essere la mancanza di indici . Quando una riga viene inserita / aggiorna / elimina, il database deve verificare i vincoli relazionali, ovvero assicurarsi che le relazioni siano coerenti. Per fare ciò, il database deve controllare le chiavi esterne nelle relative tabelle. Si potrebbe provocare altro essere acquisito serratura di riga che viene modificato. Assicurati quindi di avere sempre l'indice sulle chiavi esterne (e ovviamente sulle chiavi primarie), altrimenti potrebbe causare un blocco della tabella anziché un blocco della riga . Se si verifica il blocco della tabella, la contesa del blocco è maggiore e aumenta la probabilità di deadlock.


3
Quindi forse il mio problema è che l'utente ha aggiornato la pagina e in tal modo ha innescato un AGGIORNAMENTO di un record mentre il cron sta cercando di eseguire un DELETE sul record. Tuttavia, sto ottenendo l'errore su INSERTS, quindi il cron non sarebbe ELIMINAZIONE dei record che sono stati appena creati. Quindi, come può verificarsi un deadlock su un record che deve ancora essere inserito?
David,

Potete fornire qualche informazione in più sulle tabelle e su cosa fanno esattamente le transazioni?
ewernli,

Non vedo come potrebbe verificarsi un deadlock se esiste una sola istruzione per transazione. Nessuna altra operazione su altri tavoli? Nessuna chiave esterna speciale o vincoli univoci? Nessun vincolo di eliminazione in cascata?
ewernli,

no, nient'altro di speciale ... suppongo sia dovuto alla natura dell'uso del tavolo. una riga viene inserita / aggiornata ad ogni aggiornamento di pagina da un visitatore. Circa 1000+ visitatori sono attivi contemporaneamente.
David

12

È probabile che l'istruzione delete influenzi una grande parte delle righe totali nella tabella. Alla fine ciò potrebbe comportare l'acquisizione di un blocco tabella durante l'eliminazione. Trattenere un blocco (in questo caso i blocchi di riga o di pagina) e acquisire più blocchi è sempre un rischio di deadlock. Tuttavia, non riesco a spiegare perché l'istruzione insert porti a un'escalation del blocco: potrebbe avere a che fare con la divisione / aggiunta della pagina, ma qualcuno che conosce meglio MySQL dovrà compilare lì.

Per cominciare, vale la pena provare ad acquisire esplicitamente un blocco tabella immediatamente per l'istruzione delete. Vedere TABELLE DI BLOCCO e problemi di blocco della tabella .


6

Potresti provare a far deletefunzionare quel lavoro inserendo prima la chiave di ogni riga da eliminare in una tabella temporanea come questo pseudocodice

create temporary table deletetemp (userid int);

insert into deletetemp (userid)
  select userid from onlineusers where datetime <= now - interval 900 second;

delete from onlineusers where userid in (select userid from deletetemp);

Rompere in questo modo è meno efficiente ma evita la necessità di tenere un blocco della gamma di tasti durante il delete.

Inoltre, modifica le tue selectquery per aggiungere una whereclausola escludendo le righe più vecchie di 900 secondi. Questo evita la dipendenza dal cron job e ti consente di riprogrammare l'esecuzione meno frequente.

Teoria sui deadlock: non ho molto background in MySQL, ma ecco qui ... Il deleteblocco dei range di chiavi per il datetime verrà bloccato, per impedire che le righe corrispondenti alla sua whereclausola vengano aggiunte nel mezzo della transazione e quando trova le righe da eliminare tenterà di acquisire un blocco su ogni pagina che sta modificando. La insertsta per acquisire un blocco sulla pagina si sta inserendo in, e poi tentare di acquisire il blocco dei tasti. Normalmente insertattenderanno pazientemente l'apertura del blocco tasti, ma questo si bloccherà se si deletetenta di bloccare la stessa pagina che insertsta usando perché ha deletebisogno di quel blocco di pagine e di insertquel blocco. Questo non sembra giusto per gli inserti, deleteeinsert stanno usando intervalli di datetime che non si sovrappongono, quindi forse sta succedendo qualcos'altro.

http://dev.mysql.com/doc/refman/5.1/en/innodb-next-key-locking.html


4

Nel caso in cui qualcuno stia ancora lottando con questo problema:

Ho riscontrato un problema simile in cui 2 richieste colpivano il server contemporaneamente. Non c'era situazione come di seguito:

T1:
    BEGIN TRANSACTION
    INSERT TABLE A
    INSERT TABLE B
    END TRANSACTION

T2:
    BEGIN TRANSACTION
    INSERT TABLE B
    INSERT TABLE A
    END TRANSACTION

Quindi, ero perplesso sul perché si stesse verificando un deadlock.

Poi ho scoperto che c'era una nave di relazione figlio padre tra 2 tabelle a causa della chiave esterna. Quando stavo inserendo un record nella tabella figlio, la transazione stava acquisendo un blocco sulla riga della tabella padre. Subito dopo stavo cercando di aggiornare la riga padre che stava innescando l'elevazione del blocco su ESCLUSIVA. Poiché la seconda transazione simultanea già deteneva un blocco CONDIVISO, stava causando un deadlock.

Fare riferimento a: https://blog.tekenlight.com/2019/02/21/database-deadlock-mysql.html


Anche nel mio caso, sembra che il problema fosse una relazione di chiave esterna. Grazie1
Chris Prince,

3

Per i programmatori Java che utilizzano Spring, ho evitato questo problema utilizzando un aspetto AOP che riprova automaticamente le transazioni che si verificano in deadlock transitori.

Vedi @RetryTransaction Javadoc per maggiori informazioni.


0

Ho un metodo, i cui interni sono racchiusi in una transazione MySql.

Il problema del deadlock si è presentato per me quando ho eseguito lo stesso metodo in parallelo con se stesso.

Non si è verificato un problema durante l'esecuzione di una singola istanza del metodo.

Quando ho rimosso MySqlTransaction, sono stato in grado di eseguire il metodo in parallelo con se stesso senza problemi.

Solo condividendo la mia esperienza, non sto sostenendo nulla.


0

cronè pericoloso. Se un'istanza di cron non riesce a finire prima della successiva, è probabile che si combattano.

Sarebbe meglio avere un lavoro in esecuzione continuo che eliminasse alcune righe, dormisse alcune, quindi ripetesse.

Inoltre, INDEX(datetime)è molto importante per evitare deadlock.

Ma, se il test datetime include più del 20% della tabella, DELETEeseguirà una scansione della tabella. Pezzi più piccoli eliminati più spesso sono una soluzione alternativa.

Un altro motivo per andare con pezzi più piccoli è quello di bloccare un minor numero di righe.

Linea di fondo:

  • INDEX(datetime)
  • Attività in esecuzione continua: elimina, dormi un minuto, ripeti.
  • Per assicurarsi che l'attività sopra descritta non sia terminata, disporre di un processo cron il cui unico scopo è riavviarlo in caso di errore.

Altre tecniche di cancellazione: http://mysql.rjweb.org/doc.php/deletebig

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.