Travailler autour erreur MySQL « Deadlock trouvé en essayant d'obtenir serrure; essayez de redémarrer la transaction »

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

Question

J'ai une table MySQL avec environ 5.000.000 lignes qui sont constamment mis à jour de petites façons par des processus Perl en parallèle la connexion via DBI. Le tableau a environ 10 colonnes et plusieurs index.

Une opération assez commune donne lieu à l'erreur suivante parfois:

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

L'instruction SQL qui déclenche l'erreur est quelque chose comme ceci:

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

L'erreur est déclenchée seulement quelques fois. J'estime dans 1% des appels ou moins. Cependant, il n'a jamais eu lieu avec une petite table et est devenue plus courante que la base de données a augmenté.

Notez que je suis en utilisant le champ a_lock dans file_table pour faire en sorte que les quatre processus quasi identiques je courais ne pas essayer de travailler sur la même ligne. La limite est conçu pour briser leur travail en petits morceaux.

Je ne l'ai pas fait beaucoup accordage sur MySQL ou DBD :: mysql. MySQL est un déploiement standard Solaris et la connexion de base de données est définie comme suit:

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;

Je l'ai vu en ligne que plusieurs autres personnes ont signalé des erreurs similaires et que cela peut être une situation réelle de l'impasse.

J'ai deux questions:

  1. Qu'est-ce exactement ma situation est à l'origine de l'erreur ci-dessus?

  2. Y at-il un moyen simple de contourner ou réduire sa fréquence? Par exemple, comment exactement dois-je aller de "redémarrer la transaction à la ligne 276 Db.pm"?

Merci d'avance.

Était-ce utile?

La solution

Si vous utilisez InnoDB ou tout SGBDR transactionnelles niveau de la ligne, alors il est possible que any transaction d'écriture peut provoquer une impasse, même dans des situations parfaitement normales. tables plus grandes, plus grandes et longues écritures des blocs de transaction augmentent souvent la probabilité de blocages qui se produisent. Dans votre situation, il est probablement une combinaison de ceux-ci.

La seule façon de gérer vraiment est d'écrire des interblocages votre code pour les attendre. Ceci est généralement pas très difficile si votre code de base de données est bien écrit. Souvent, vous pouvez simplement mettre un try/catch autour de la logique d'exécution de la requête et rechercher une impasse lorsque des erreurs se produisent. Si vous attrapez un, la chose normale à faire est de tenter simplement d'exécuter la requête à nouveau échoué.

Je recommande fortement de lire cette page le manuel MySQL. Il a une liste de choses à faire pour aider à faire face et réduire leur blocages fréquence.

Autres conseils

La réponse est correcte, mais la documentation perl sur la façon de gérer est un peu des interblocages rares et peut-être source de confusion avec des options PrintError, raiseError et HandleError. Il semble que, plutôt que d'aller avec HandleError, utiliser sur Imprimer et Raise puis utiliser quelque chose comme Essayer: minuscule pour envelopper votre code et vérifier les erreurs. Le code ci-dessous donne un exemple où le code db se trouve dans une boucle while qui ré-exécuter une instruction SQL Errored toutes les 3 secondes. Le bloc catch obtient $ _ qui est le message err spécifique. Je passe à un fonction de gestionnaire « dbi_err_handler » qui vérifie $ _ contre toute une série d'erreurs et renvoie 1 si le code devrait continuer (rompant ainsi la boucle) ou 0 si son impasse et devrait être rejugé ...

$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 doit avoir au moins les éléments suivants:

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

Vous devez inclure d'autres erreurs que vous souhaitez gérer et ensemble valret $ selon que vous souhaitez réexécuter ou continuer ..

Hope this helps quelqu'un -

Notez que si vous utilisez SELECT FOR UPDATE pour effectuer une vérification unique avant un insert, vous obtiendrez une impasse pour toutes les conditions de course, sauf si vous activez l'option innodb_locks_unsafe_for_binlog. Procédé sans verrouillage pour vérifier l'unicité est aveugle à insérer une ligne dans une table avec un index unique en utilisant INSERT IGNORE, puis de vérifier le nombre de lignes affectées.

ajouter à la ligne ci-dessous le fichier my.cnf

innodb_locks_unsafe_for_binlog = 1

#

1 -
0 - OFF

#

L'idée de relancer la demande en cas d'exception Deadlock est bon, mais il peut être terriblement lent, car la requête MySQL gardera en attente de verrous pour être libérés. Et Incase de MySQL de blocage tente de trouver s'il y a une impasse, et même après avoir découvert qu'il ya une impasse, il attend un certain temps avant coups de pied un fil afin de sortir de la situation de blocage.

Ce que je faisais quand je faisais face à cette situation est de mettre en œuvre le verrouillage dans votre propre code, car il est le mécanisme de verrouillage de MySQL échoue en raison d'un bogue. Donc, j'ai mis mon propre verrouillage de niveau ligne dans mon code 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
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top