MySql Gap Lock Deadlock sur les inserts

8

J'obtiens des verrous mortels à partir de verrous d'espacement sur une table lorsque j'insère fréquemment dedans à partir de plusieurs sources. Voici un aperçu de mes processus.

START TRANSACTION
  UPDATE vehicle_image
  SET active = 0
  WHERE vehicleID = SOMEID AND active = 1

  Loop:
    INSERT INTO vehicle_image (vehicleID, vehicleImageFilePath, vehicleImageSplashFilePath
      ,vehicleImageThumbnailFilePath, vehicleImageMiniFilePath, mainVehicleImage, active)
    VALUES (%s, %s, %s, %s, %s, %s, 1);
END TRANSACTION

La sortie de SHOW Create table vehicle_image;est:

CREATE TABLE `vehicle_image` (
  `vehicleImageID` int(11) NOT NULL AUTO_INCREMENT,
  `vehicleID` int(11) DEFAULT NULL,
  `vehicleImageFilePath` varchar(200) DEFAULT NULL,
  `vehicleImageSplashFilePath` varchar(200) DEFAULT NULL,
  `vehicleImageThumbnailFilePath` varchar(200) DEFAULT NULL,
  `vehicleImageMiniFilePath` varchar(200) DEFAULT NULL,
  `mainVehicleImage` bit(1) DEFAULT NULL,
  `active` bit(1) DEFAULT b'1',
  `userCreated` int(11) DEFAULT NULL,
  `dateCreated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `userModified` int(11) DEFAULT NULL,
  `dateModified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`vehicleImageID`),
  KEY `active` (`active`),
  KEY `mainvehicleimage` (`mainVehicleImage`),
  KEY `vehicleid` (`vehicleID`)
) ENGINE=InnoDB AUTO_INCREMENT=22878102 DEFAULT CHARSET=latin1

Et le dernier Deadlock donné par SHOW engine innodb status:

LATEST DETECTED DEADLOCK
------------------------
2018-03-27 12:31:15 11a58
*** (1) TRANSACTION:
TRANSACTION 5897678083, ACTIVE 2 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1248, 2 row lock(s), undo log entries 1
MySQL thread id 873570, OS thread handle 0x124bc, query id 198983754 ec2-34-239-240-179.compute-1.amazonaws.com 34.239.240.179 image_processor update
INSERT INTO vehicle_image (vehicleID, vehicleImageFilePath, vehicleImageSplashFilePath, vehicleImageThumbnailFilePath, vehicleImageMiniFilePath, mainVehicleImage, active)
VALUES (70006176, 'f180928(1)1522168276.230837full.jpg', 'f180928(1)1522168276.230837splash.jpg', 'f180928(1)1522168276.230837thumb.jpg', 'f180928(1)1522168276.230837mini.jpg', 1, 1)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 875 page no 238326 n bits 472
  index `vehicleid` of table `ipacket`.`vehicle_image` trx id 5897678083
  lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 378 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 842c365a; asc  ,6Z;;
 1: len 4; hex 815d03bc; asc  ]  ;;

*** (2) TRANSACTION:
TRANSACTION 5897678270, ACTIVE 1 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1248, 2 row lock(s), undo log entries 1
MySQL thread id 873571, OS thread handle 0x11a58, query id 198983849 ec2-35-171-169-21.compute-1.amazonaws.com 35.171.169.21 image_processor update
INSERT INTO vehicle_image (vehicleID, vehicleImageFilePath, vehicleImageSplashFilePath, vehicleImageThumbnailFilePath, vehicleImageMiniFilePath, mainVehicleImage, active)
VALUES (70006326, '29709(1)1522168277.4443843full.jpg', '29709(1)1522168277.4443843splash.jpg', '29709(1)1522168277.4443843thumb.jpg', '29709(1)1522168277.4443843mini.jpg', 1, 1)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 875 page no 238326 n bits 464
  index `vehicleid` of table `ipacket`.`vehicle_image` trx id 5897678270
  lock_mode X locks gap before rec
Record lock, heap no 378 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 842c365a; asc  ,6Z;;
 1: len 4; hex 815d03bc; asc  ]  ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 875 page no 238326 n bits 472
  index `vehicleid` of table `ipacket`.`vehicle_image` trx id 5897678270
  lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 378 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 842c365a; asc  ,6Z;;
 1: len 4; hex 815d03bc; asc  ]  ;;

*** WE ROLL BACK TRANSACTION (2)

J'exécute plusieurs de ces processus simultanément, mais je n'exécute jamais deux processus qui utilisent le même VehicleID. Je ne comprends vraiment pas pourquoi je reçois des blocages.

J'ai résolu temporairement le problème en utilisant le niveau d'isolement READ COMMITTED, mais j'ai lu que cela nécessite des modifications de réplication dans la mesure où vous devez effectuer une réplication au niveau des lignes.

J'ai lu d'autres questions ici qui sont similaires aux miennes, mais je suis un peu nouveau pour SQL et je ne comprends toujours pas pourquoi cela se produit.

Questions similaires:
- Blocage sur les insertions MySQL
- Blocage MySQL InnoDB Pour 2 requêtes d'insertion simples

MISE À JOUR:

J'ai découvert que l'utilisation READ COMMITTEDn'a pas vraiment résolu le problème. Je n'ai toujours pas compris pourquoi les blocages se produisent et je ne sais vraiment pas comment diagnostiquer plus loin que ce que j'ai actuellement. Je continue d'avoir des impasses dans mon système de production. Toute aide serait appréciée.

Brian Sizemore
la source
Pourriez-vous nous donner plus de détails - en particulier: la configuration du disque, le nombre de plateaux, les performances? RAM, combien? CPU, nombre et performances? Nombre de transactions par seconde, par minute, par heure et par jour? Ces tarifs varient-ils dans le temps? Comment, exactement, ces blocages affectent-ils les performances? Donnez-nous la sortie de SHOW PROCESSLIST;. La plupart du temps, REPEATABLE READc'est le meilleur niveau d'isolement pour la plupart des applications, donc je ne serais pas trop préoccupé par son utilisation. Y a-t-il eu une augmentation notable des performances lorsque vous l'avez modifiée par défaut - REPEATABLE READ?
Vérace
Comment cela peut-il fonctionner? Vous avez des chaînes non entre guillemets VARCHARs.
Rick James
Où est la boucle finale?
Rick James
@RickJames Je n'ai pas de chaînes sans guillemets entrant dans VARCHARS, les requêtes fonctionnent comme prévu lorsqu'elles sont exécutées 95% du temps. La boucle de fin est indiquée par la tabulation au même niveau. Par exemple, la boucle commence, j'exécute cette instruction d'insertion plusieurs fois, puis la boucle se termine et la transaction se termine. Notez que le looppseudocode est juste pour représenter ce qui se passe.
Brian Sizemore
@ Vérace Repeatable Read est la valeur par défaut pour cette table (en utilisant le moteur innodb). J'ai testé changer de repeatable readà read committedqui est un niveau d'isolation plus faible puis de lecture répétée, mais malheureusement cela n'a pas empêché les blocages. Je sais que le matériel affectera le serveur (c'est une instance ec2, je devrais rechercher des détails) mais je ne pense pas que ces informations seraient nécessaires pour comprendre pourquoi les blocages se produisent. La nature sporadique de ceci rend également difficile la capture de la sortie de show processlist; lorsque l'impasse se produit.
Brian Sizemore

Réponses:

4

Je ne suis pas un expert MySQL, mais d'après l'apparence de vos journaux Deadlock, même si vous insérez différents ID de véhicule par instruction, ceux-ci nécessitent que la page de données entière (238326) de l' VehicleIDindex non cluster soit verrouillée .

Le fait que vous ayez parfois des blocages signifie que sur 1 page, vous avez plusieurs ID de véhicule, il y a donc une petite chance que 2 processus différents aient besoin d'un verrou pour la même page.

La meilleure chose à conseiller est de garder vos transactions aussi petites que possible .

S'il existe un moyen de procéder comme suit, cela contribuera à réduire les risques de blocage:

START TRANSACTION;
  UPDATE vehicle_image SET active = 0 WHERE vehicleID = SOMEID and active = 1;
END TRANSACTION;
Loop:
  START TRANSACTION;
  INSERT INTO vehicle_image (vehicleID, vehicleImageFilePath,
    vehicleImageSplashFilePath, vehicleImageThumbnailFilePath,
    vehicleImageMiniFilePath, mainVehicleImage, active)
  VALUES (%s, %s, %s, %s, %s, %s, 1);  
  END TRANSACTION;
--EndLoop here

Si vous le pouvez, essayez de changer le facteur de remplissage de cet index à 95% et testez pour voir si vous obtenez moins de blocages.

Un test plus extrême serait de supprimer complètement cet index lors de l'insertion, puis de le recréer une fois terminé.

Oreo
la source
Avez-vous une idée de pourquoi la page entière serait verrouillée par rapport à la ou aux lignes que j'essaye d'insérer?
Brian Sizemore
1
De plus, je vais refaire un peu mon code et réduire le temps de transaction. Je pense que vous avez raison de dire que cela devrait faire une différence significative.
Brian Sizemore
Je ne sais pas comment fonctionnent les internes de MySQL, mais cette réponse l' explique pour MS SQL. Quelques bons conseils MySQL dans le manuel MySQL aussi.
Oreo
1
MySQL ne fournit aucun contrôle sur fillfactor.
Rick James
2
Après avoir refactorisé mon code pour mettre en file d'attente mes insertions et l'instruction de mise à jour et les avoir exécutées très près les unes des autres, cela a résolu mon problème. Non seulement cela, mais j'ai pu continuer à augmenter cela (environ le double de la quantité précédente de processus parallèles) et cela fonctionne toujours correctement. Merci Oreo!
Brian Sizemore
1

MySQL verrouille non seulement la ligne affectée, mais également la ligne d'index affectée et l'écart entre les lignes d'index (comme décrit ici ). Étant donné que les clés primaires sont toujours indexées et que vous les utilisez dans vos mises à jour, je soupçonne que plusieurs transactions essayant de mettre à jour plusieurs lignes entraînent chacune des verrous d'écart d'index qui se chevauchent, ce qui crée à son tour le blocage.

Pour résoudre ce problème, je recommande également les conseils d'Oreos pour maintenir une transaction aussi petite que possible. Si les lignes mises à jour sont indépendantes les unes des autres, vous devez utiliser une transaction distincte pour chacune d'entre elles.

Flourid
la source