Lavorando intorno all'errore MySQL “Stallo trovato quando si cerca di ottenere il blocco; provare a riavviare transazione”

StackOverflow https://stackoverflow.com/questions/2596005

Domanda

Ho una tabella MySQL con circa 5.000.000 righe che vengono costantemente aggiornate in piccoli modi di processi Perl paralleli che collegano via DBI. La tabella ha circa 10 colonne e vari indici.

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 inneschi 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. Mi piacerebbe stima in 1% di chiamate o meno. Tuttavia, non è mai successo con un piccolo tavolo ed è diventato più comune, come il database è cresciuto.

Si noti che sto usando il campo a_lock in file_table per garantire che i quattro vicino-identici processi Sono in esecuzione non provare e lavorare sulla stessa riga. Il limite è stato progettato per rompere il loro lavoro in piccoli pezzi.

Non ho fatto molto di sintonia su MySQL o DBD :: mysql. MySQL è una distribuzione standard di Solaris, e la connessione al database è configurato 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 è esattamente la mia situazione sta causando l'errore precedente?

  2. C'è un modo semplice per aggirare o ridurre la sua frequenza? Per esempio, come esattamente posso fare per "riavviare transazione al Db.pm linea 276"?

Grazie in anticipo.

È stato utile?

Soluzione

Se si utilizza InnoDB o qualsiasi RDBMS transazionale a livello di riga, allora è possibile che qualsiasi operazione di scrittura può causare una situazione di stallo, anche in situazioni perfettamente normali. tavoli più grandi, scrive più grandi, e blocchi di transazione lunghe spesso aumentare la probabilità di deadlock che si verificano. Nella tua situazione, è probabilmente una combinazione di questi.

L'unico modo per gestire veramente deadlock è quello di scrivere il codice per aspettare loro. Questo in genere non è molto difficile se il codice del database è ben scritto. Spesso si può semplicemente mettere un try/catch intorno alla logica esecuzione di query e cercare una situazione di stallo quando si verificano errori. Se si cattura uno, la cosa normale da fare è tentare di eseguire nuovamente la query non è riuscita.

mi raccomando di leggere questa pagina il manuale di MySQL. Ha una lista di cose da fare per far fronte alle situazioni di stallo e di ridurre la loro frequenza.

Altri suggerimenti

La risposta è corretta, ma la documentazione di Perl su come gestire situazioni di stallo è un po 'scarsa e forse confondendo con le opzioni PrintError, RaiseError e HandleError. Sembra che piuttosto che andare con HandleError, l'uso su Stampa e sollevare e quindi utilizzare qualcosa come Prova: Normale per avvolgere il codice e controllare gli errori. Il sotto codice riportato un esempio in cui il codice db è all'interno di un ciclo while che si ri-eseguire un'istruzione SQL con errori ogni 3 secondi. Il blocco catch ottiene $ _ che è il messaggio err specifica. Io passo questo per una funzione del gestore "dbi_err_handler", che controlla $ _ contro una serie di errori e restituisce 1 se il codice dovrebbe continuare (rompendo così il loop) o 0 se una situazione di stallo e di cui riprovare ...

$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 -

Si noti che se si utilizza SELECT FOR UPDATE per eseguire un controllo di unicità prima di un inserto, si otterrà una situazione di stallo per ogni condizione di competizione se non si attiva l'opzione innodb_locks_unsafe_for_binlog. Procedimento privo di stallo per controllare unicità è ciecamente inserto una riga in una tabella con un indice univoco utilizzando INSERT IGNORE, quindi controllare il conteggio riga interessata.

add sotto la linea di file di my.cnf

innodb_locks_unsafe_for_binlog = 1

#

1 - ON
0 - OFF

#

L'idea di riprovare la query in caso di eccezione Stallo è buona, ma può essere terribilmente lento, dal momento che mysql query manterrà in attesa di serrature per essere rilasciato. E in caso di stallo mysql sta cercando di trovare se c'è qualche situazione di stallo, e anche dopo aver scoperto che v'è una situazione di stallo, aspetta un po 'prima di calciare fuori un filo al fine di uscire dalla situazione di stallo.

Quello che ho fatto quando ho affrontato questa situazione è quello di implementare il blocco nel proprio codice, dal momento che è il meccanismo di bloccaggio di mysql 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
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top