2010-04-07 16 views
25

Ho una tabella MySQL con circa 5.000.000 di file che vengono costantemente aggiornate in piccoli modi da processi Perl paralleli che si connettono tramite DBI. La tabella ha circa 10 colonne e diversi indici.Errore MySQL "Quando si cerca di ottenere il blocco, prova a riavviare la transazione"

Un'operazione piuttosto comune dà luogo alla seguente errore a volte:

DBD::mysql::st execute failed: Deadlock found when trying to get lock; try restarting transaction at Db.pm line 276. 

L'istruzione SQL che fa scattare l'errore è qualcosa di simile:

UPDATE file_table SET a_lock = 'process-1234' WHERE param1 = 'X' AND param2 = 'Y' AND param3 = 'Z' LIMIT 47 

L'errore si attiva solo a volte. Stimo nell'1% delle chiamate o meno. Tuttavia, non è mai successo con un piccolo tavolo ed è diventato più comune man mano che il database è cresciuto.

Si noti che sto utilizzando il campo a_lock in file_table per garantire che i quattro processi quasi identici in esecuzione non provino e lavorino sulla stessa riga. Il limite è progettato per rompere il loro lavoro in piccoli pezzi.

Non ho eseguito molta ottimizzazione su MySQL o DBD :: mysql. MySQL è una distribuzione standard di Solaris, e la connessione al database è impostato come segue:

my $dsn = "DBI:mysql:database=" . $DbConfig::database . ";host=${DbConfig::hostname};port=${DbConfig::port}"; 
my $dbh = DBI->connect($dsn, $DbConfig::username, $DbConfig::password, { RaiseError => 1, AutoCommit => 1 }) or die $DBI::errstr; 

Ho visto online che molte altre persone hanno segnalato errori simili e che questo può essere una situazione di vera e propria situazione di stallo.

Ho due domande:

  1. Che cosa circa la mia situazione sta causando l'errore di cui sopra?

  2. C'è un modo semplice per aggirare o ridurre la sua frequenza? Ad esempio, come faccio esattamente a "riavviare la transazione alla riga Db.pm 276"?

Grazie in anticipo.

risposta

61

Se si utilizza InnoDB o qualsiasi livello di riga RDBMS transazionali, allora è possibile che qualsiasi transazione scrittura può causare una situazione di stallo, anche in situazioni perfettamente normali. Tabelle più grandi, scritture più grandi e blocchi di transazioni lunghi spesso aumentano la probabilità che si verifichino deadlock. Nella tua situazione, probabilmente è una combinazione di questi.

L'unico modo per gestire veramente i deadlock è scrivere il codice per aspettarseli. Questo in genere non è molto difficile se il codice del tuo database è ben scritto. Spesso puoi semplicemente inserire un try/catch intorno alla logica di esecuzione della query e cercare un deadlock quando si verificano errori. Se ne prendi una, la cosa normale da fare è solo tentare di eseguire nuovamente la query fallita.

Consiglio vivamente di leggere this page nel manuale MySQL. Ha una lista di cose da fare per aiutare a far fronte alle situazioni di stallo e ridurne la frequenza.

+2

Quali sono i codici di errore che dobbiamo catturare? Basta prendere 1205 da solo? Ci sono oltre 900 codici di errore in http://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html. Come fai a sapere tutti i codici che dobbiamo prendere per implementare una soluzione adeguata per il tuo suggerimento di prova/presa? – Pacerier

+0

Questo significa che oltre a "InnoDB o qualsiasi RDBMS transazionale a livello di riga" non hanno questi problemi? –

5

Si noti che se si utilizza SELECT FOR UPDATE per eseguire un controllo di unicità prima di un inserimento, si otterrà un deadlock per ogni condizione di gara a meno che non si abiliti l'opzione innodb_locks_unsafe_for_binlog. Un metodo deadlock-free per verificare l'unicità è inserire ciecamente una riga in una tabella con un indice univoco usando INSERT IGNORE, quindi controllare il conteggio delle righe interessate.

aggiungere sotto la linea di my.cnf file di

innodb_locks_unsafe_for_binlog = 1

#

1 - ON
0 - OFF

#
+0

Questo ha risolto tutti i miei problemi con il salvataggio delle associazioni ActiveRecord in ambienti con multithreading. – lightyrs

+2

Abilitare 'innodb_locks_unsafe_for_binlog' può causare problemi fantasma perché altre sessioni possono inserire nuove righe negli spazi vuoti quando il blocco del gap è disabilitato. – shivam

9

La risposta è corretta, ma la documentazione di Perl su come gestire deadlock è un po 'scarso e forse confondere con PrintError, RaiseError e HandleError opzioni. Sembra che piuttosto che andare con HandleError, utilizzare su Stampa e Alza e quindi utilizzare qualcosa come Try: Tiny per avvolgere il codice e verificare la presenza di errori. Il codice seguente fornisce un esempio in cui il codice db si trova all'interno di un ciclo while che eseguirà nuovamente una istruzione sql errata ogni 3 secondi. Il blocco catch ottiene $ _ che è lo specifico messaggio err. Trasmetto questo a una funzione di gestore "dbi_err_handler" che controlla $ _ contro un host di errori e restituisce 1 se il codice deve continuare (interrompendo il ciclo) o 0 se è un deadlock e deve essere ritentato ...

$sth = $dbh->prepare($strsql); 
my $db_res=0; 
while($db_res==0) 
{ 
    $db_res=1; 
    try{$sth->execute($param1,$param2);} 
    catch 
    { 
     print "caught $_ in insertion to hd_item_upc for upc $upc\n"; 
     $db_res=dbi_err_handler($_); 
     if($db_res==0){sleep 3;} 
    } 
} 

dbi_err_handler dovrebbe avere almeno la seguente:

sub dbi_err_handler 
{ 
    my($message) = @_; 
    if($message=~ m/DBD::mysql::st execute failed: Deadlock found when trying to get lock; try restarting transaction/) 
    { 
     $caught=1; 
     $retval=0; # we'll check this value and sleep/re-execute if necessary 
    } 
    return $retval; 
} 

È necessario includere altri errori che si desidera gestire e set $ RETVAL a seconda se si desidera ri-eseguire o continuare ..

Spero che questo aiuti qualcuno -

0

L'idea di riprovare la query in caso di eccezione di deadlock è buona, ma può essere terribilmente lenta, poiché la query mysql continuerà ad attendere il rilascio dei blocchi. E nel caso di deadlock mysql sta cercando di scoprire se c'è un deadlock, e anche dopo aver scoperto che c'è un deadlock, aspetta un po 'prima di tirare fuori un thread per uscire dalla situazione di deadlock.

Quello che ho fatto quando ho affrontato questa situazione è implementare il blocco nel proprio codice, poiché è il meccanismo di blocco di mysql che non riesce a causa di un bug. Così ho implementato il mio blocco a livello di riga nel mio codice java:

private HashMap<String, Object> rowIdToRowLockMap = new HashMap<String, Object>(); 
private final Object hashmapLock = new Object(); 
public void handleShortCode(Integer rowId) 
{ 
    Object lock = null; 
    synchronized(hashmapLock) 
    { 
     lock = rowIdToRowLockMap.get(rowId); 
     if (lock == null) 
     { 
      rowIdToRowLockMap.put(rowId, lock = new Object()); 
     } 
    } 
    synchronized (lock) 
    { 
     // Execute your queries on row by row id 
    } 
} 
+4

Sfortunatamente è probabile che la maggior parte degli utenti che incontrano questo si occupi di più macchine o processi che scaricano dati in una singola istanza MySQL. Il blocco a livello di riga nell'applicazione non è semplicemente un'opzione per la maggior parte degli utenti. – dgtized

Problemi correlati