2010-04-07 3 views
25

Ich habe eine MySQL-Tabelle mit etwa 5.000.000 Zeilen, die ständig in kleinen Wegen durch parallele Perl-Prozesse via DBI-Verbindung aktualisiert werden. Die Tabelle hat ungefähr 10 Spalten und mehrere Indizes.Um MySQL-Fehler zu arbeiten "Deadlock gefunden, wenn versucht, Sperre zu bekommen; versuchen, Transaktion neu zu starten"

Eine recht häufige Operation führt zu folgendem Fehler manchmal:

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

Die SQL-Anweisung, die den Fehler auslöst, ist so etwas wie dieses:

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

Der Fehler nur manchmal ausgelöst wird. Ich würde in 1% der Anrufe oder weniger schätzen. Es ist jedoch nie mit einer kleinen Tabelle passiert und ist häufiger geworden, als die Datenbank gewachsen ist.

Beachten Sie, dass ich das a_lock-Feld in file_table verwende, um sicherzustellen, dass die vier nahezu identischen Prozesse, die ich ausführe, nicht versuchen, in derselben Zeile zu arbeiten. Das Limit ist darauf ausgelegt, ihre Arbeit in kleine Stücke zu zerlegen.

Ich habe nicht viel Tuning auf MySQL oder DBD :: MySQL gemacht. MySQL ist eine Standard-Solaris-Implementierung und die Datenbankverbindung wird wie folgt festgelegt:

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; 

Ich habe online gesehen, dass mehrere andere Menschen Ähnliche Fehler gemeldet haben, und dass dies eine echte Deadlock-Situation sein.

Ich habe zwei Fragen:

  1. Was genau über meine Situation über den Fehler verursacht?

  2. Gibt es einen einfachen Weg, um es zu umgehen oder seine Frequenz zu verringern? Zum Beispiel, wie genau gehe ich "Neustart der Transaktion bei Db.pm Linie 276"?

Vielen Dank im Voraus.

Antwort

61

Wenn Sie InnoDB oder jede Zeilenebene Transaktions RDBMS verwenden, dann ist es möglich, dass jede Schreibtransaktion zu einem Deadlock führen kann, auch in ganz normalen Situationen. Größere Tabellen, größere Schreibvorgänge und lange Transaktionsblöcke erhöhen häufig die Wahrscheinlichkeit von Deadlocks. In Ihrer Situation ist es wahrscheinlich eine Kombination von diesen.

Die einzige Möglichkeit, Deadlocks wirklich zu behandeln, besteht darin, Ihren Code zu schreiben, um sie zu erwarten. Dies ist im Allgemeinen nicht sehr schwierig, wenn Ihr Datenbankcode gut geschrieben ist. Oft können Sie einfach einen try/catch um die Abfrageausführungslogik legen und nach einem Deadlock suchen, wenn Fehler auftreten. Wenn Sie einen fangen, ist es normal zu versuchen, die fehlgeschlagene Abfrage erneut auszuführen.

Ich empfehle Ihnen, lesen Sie this page im MySQL-Handbuch. Es gibt eine Liste von Dingen, die Sie tun können, um mit Deadlocks fertig zu werden und deren Häufigkeit zu reduzieren.

+2

Was sind die Fehlercodes, die wir dann fangen müssen? Fängt 1205 allein ausreicht? Es gibt über 900 Fehlercodes in http://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html. Woher wissen Sie alle Codes, die wir fangen müssen, um eine richtige Lösung für Ihren Versuch/Fang-Vorschlag zu implementieren? – Pacerier

+0

Bedeutet dies, dass andere als "InnoDB oder irgendein Transaktions-RDBMS auf Zeilenebene" diese Probleme nicht haben? –

5

Wenn Sie SELECT FOR UPDATE verwenden, um eine Eindeutigkeitsprüfung vor einer Einfügung durchzuführen, erhalten Sie einen Deadlock für jede Race Condition, es sei denn, Sie aktivieren die Option innodb_locks_unsafe_for_binlog. Eine Deadlock-freie Methode zum Überprüfen der Eindeutigkeit besteht darin, eine Zeile blind in eine Tabelle mit einem eindeutigen Index einzufügen, indem INSERT IGNORE verwendet wird, und dann die Anzahl der betroffenen Zeilen zu überprüfen.

unterhalb der Linie zu my.cnf Datei hinzufügen

innodb_locks_unsafe_for_binlog = 1

#

1 - ON
0 - OFF

#
+0

Dies löste alle meine Probleme mit dem Speichern von ActiveRecord Assoziationen in Multithread-Umgebungen. – lightyrs

+2

Das Aktivieren von "innodb_locks_unsafe_for_binlog" kann Phantomprobleme verursachen, da andere Sitzungen neue Zeilen in die Lücken einfügen können, wenn die Lückensperrung deaktiviert ist. – shivam

9

Die Antwort richtig ist, aber die Perl-Dokumentation Wie man mit Deadlocks umgeht, ist etwas spärlich und vielleicht verwirrend mit PrintError, RaiseError und HandleError Optionen. Es scheint, dass anstatt mit HandleError zu gehen, verwenden Sie auf Drucken und Raise und verwenden Sie dann etwas wie Try: Tiny, um Ihren Code einzupacken und auf Fehler zu prüfen. Der folgende Code gibt ein Beispiel, in dem der DB-Code innerhalb einer while-Schleife ist, die eine fehlerhafte SQL-Anweisung alle 3 Sekunden erneut ausführt. Der catch-Block erhält $ _, was die spezifische Fehlermeldung ist. Ich übergebe dies an eine Handler-Funktion "dbi_err_handler", die $ _ gegen einen Host von Fehlern prüft und 1 zurückgibt, wenn der Code fortfahren sollte (dadurch die Schleife zu brechen) oder 0, wenn es sich um einen Deadlock handelt und erneut versucht werden soll.

$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 sollte die folgende zumindest:

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; 
} 

Sie andere Fehler enthalten, sollten Sie auf zu handhaben und Set $ ​​retval wollen je nachdem, ob Sie erneut ausführen oder fortsetzen möchten ..

Hoffe, das hilft jemandem -

0

Die Idee, die Abfrage im Falle einer Deadlock-Ausnahme erneut zu versuchen, ist gut, aber sie kann furchtbar langsam sein, da die MySQL-Abfrage weiterhin darauf wartet, dass Sperren freigegeben werden. Und im Falle von Deadlock versucht mysql herauszufinden, ob es einen Deadlock gibt, und selbst nachdem er herausgefunden hat, dass ein Deadlock vorliegt, wartet er eine Weile, bevor er einen Thread rausschmeißt, um aus der Deadlock-Situation herauszukommen.

Was ich getan habe, als ich mit dieser Situation konfrontiert wurde, ist das Sperren in Ihrem eigenen Code zu implementieren, da es der Sperrmechanismus von mysql aufgrund eines Fehlers fehlschlägt. Also habe ich meine eigene Zeilensperre in meinem Java Code implementiert:

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

Leider ist es wahrscheinlich, dass die meisten Benutzer mit mehreren Maschinen oder Prozessen konfrontiert sind, die Daten in einer einzelnen MySQL-Instanz ablegen. Das Sperren auf Zeilenebene in der Anwendung ist für die meisten Benutzer einfach keine Option. – dgtized

Verwandte Themen