À propos du comportement Model.save () de Django et de l'erreur de blocage MySQL

introduction

Lorsque j'ai essayé de sauvegarder un enregistrement en utilisant Model.save () de Django, j'ai rencontré une erreur de blocage MySQL, alors j'ai recherché la raison.

supposition

Django1.8.4, MySQL5.5 InnoDB, le niveau d'isolation de transaction est REPEATABLE-READ.

À propos de Model.save () de Django

Citez https://docs.djangoproject.com/en/1.8/ref/models/instances/#how-django-knows-to-update-vs-insert.

You may have noticed Django database objects use the same save() method for creating and changing objects. Django abstracts the need to use INSERT or UPDATE SQL statements. Specifically, when you call save(), Django follows this algorithm:

  • If the object’s primary key attribute is set to a value that evaluates to True (i.e., a value other than None or the empty string), Django executes an UPDATE.
  • If the object’s primary key attribute is not set or if the UPDATE didn’t update anything, Django executes an INSERT.

Django semble rendre l'utilisateur ignorant de INSERT ou UPDATE lors de l'utilisation de save (). Émettez un UPDATE si la clé primaire de l'instance peut être évaluée comme True. Il semble que INSERT est émis à nouveau lorsque la ligne modifiée est 0 dans cette UPDATE.

Exemple de dead lock

Préparez le modèle suivant. Derived est un modèle qui hérite de Base.

models.py


from django.db import models

class Base(models.Model):
    a = models.IntegerField()

class Derived(Base):
    b = models.IntegerField()

Et si vous essayez de générer un enregistrement avec Derived en utilisant Model.save ()? La réponse est que si vous effectuez le processus de génération d'enregistrement simple suivant en parallèle à haute fréquence, vous obtiendrez une erreur de blocage MySQL ʻERROR 1213 (40001): Blocage trouvé lors de la tentative de verrouillage; essayez de redémarrer la transaction`. Je vais.

create_derived.py


d = Derived(a=1, b=2)
d.save()

=> ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction !!!!

Raison

Tout d'abord, vérifiez la définition de table de model.py. L'instruction CREATE TABLE ressemble à ceci: Le modèle dérivé contient base_ptr_id comme informations de source d'héritage et est une clé PRIMAIRE.

CREATE TABLE `ham_base` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `a` int(11) NOT NULL,
  PRIMARY KEY (`id`)
)

CREATE TABLE `ham_derived` (
  `base_ptr_id` int(11) NOT NULL,
  `b` int(11) NOT NULL,
  PRIMARY KEY (`base_ptr_id`),
  CONSTRAINT `ham_derived_base_ptr_id_12f18f813c81ff4f_fk_ham_base_id` FOREIGN KEY (`base_ptr_id`) REFERENCES `ham_base` (`id`)
)

Ensuite, vérifiez la requête émise à save () de create_derived.py.

INSERT INTO `ham_base` (`a`) VALUES (1);
UPDATE `ham_derived` SET `b` = 2 WHERE `ham_derived`.`base_ptr_id` = 1 ; args=(2, 1)
INSERT INTO `ham_derived` (`base_ptr_id`, `b`) VALUES (1, 2); args=(1, 2)

Il semble que la deuxième UPDATE manquée soit une habitude et obtienne un verrou d'intervalle lorsque le niveau d'isolement de transaction est REPEATABLE-READ. En fonction du moment choisi, les situations suivantes se produiront et un blocage se produira.

Transaction1 Transaction2 commentaire
INSERT INTO ham_base (a) VALUES (1);
INSERT INTO ham_base (a) VALUES (1);
UPDATE ham_derived SET b = 2 WHERE ham_derived.base_ptr_id = 1; Verrouiller une acquisition
UPDATE ham_derived SET b = 2 WHERE ham_derived.base_ptr_id = 2; Acquisition de verrouillage B
INSERT INTO ham_derived (base_ptr_id, b) VALUES (1, 2) En attente du verrou B
INSERT INTO ham_derived (base_ptr_id, b) VALUES (2, 2) Puisqu'il attend le verrou A, le verrou mort est immédiatement détecté.

solution de contournement

Tout ce que vous avez à faire est d'indiquer clairement qu'il s'agit d'un INSERT. Vous pouvez l'éviter par les méthodes suivantes.

--Utilisez Model.objects.create () --Utilisez l'option force_insert de Model.save (). --Spécifiez select_on_save = True dans l'option méta du modèle.

> Determines if Django will use the pre-1.6 django.db.models.Model.save() algorithm. The old algorithm uses SELECT to determine if there is an existing row to be updated. The new algorithm tries an UPDATE directly.

Recommended Posts

À propos du comportement Model.save () de Django et de l'erreur de blocage MySQL
À propos de la déconstruction et de la déconstructibilité de Django
À propos de _ et __
À propos du comportement de copy, deepcopy et numpy.copy
À propos du ProxyModel de Django
À propos de l'erreur d'importation de numpy et scipy dans anaconda