Ответ 1
Можно ли воспроизвести ошибку?
Да, давайте использовать знаменитые модели Publication
и Article
из Django docs. Затем создайте несколько потоков.
import threading
import random
def populate():
for i in range(100):
Article.objects.create(headline = 'headline{0}'.format(i))
Publication.objects.create(title = 'title{0}'.format(i))
print 'created objects'
class MyThread(threading.Thread):
def run(self):
for q in range(1,100):
for i in range(1,5):
pub = Publication.objects.all()[random.randint(1,2)]
for j in range(1,5):
article = Article.objects.all()[random.randint(1,15)]
pub.article_set.add(article)
print self.name
Article.objects.all().delete()
Publication.objects.all().delete()
populate()
thrd1 = MyThread()
thrd2 = MyThread()
thrd3 = MyThread()
thrd1.start()
thrd2.start()
thrd3.start()
Вы обязательно увидите уникальные нарушения ограничений типа, указанные в отчете об ошибке . Если вы их не видите, попробуйте увеличить количество потоков или итераций.
Есть ли работа вокруг?
Да. Используйте модели through
и get_or_create
. Вот модели .py, адаптированные из примера в django docs.
class Publication(models.Model):
title = models.CharField(max_length=30)
def __str__(self): # __unicode__ on Python 2
return self.title
class Meta:
ordering = ('title',)
class Article(models.Model):
headline = models.CharField(max_length=100)
publications = models.ManyToManyField(Publication, through='ArticlePublication')
def __str__(self): # __unicode__ on Python 2
return self.headline
class Meta:
ordering = ('headline',)
class ArticlePublication(models.Model):
article = models.ForeignKey('Article', on_delete=models.CASCADE)
publication = models.ForeignKey('Publication', on_delete=models.CASCADE)
class Meta:
unique_together = ('article','publication')
Вот новый класс потоков, который является модификацией выше.
class MyThread2(threading.Thread):
def run(self):
for q in range(1,100):
for i in range(1,5):
pub = Publication.objects.all()[random.randint(1,2)]
for j in range(1,5):
article = Article.objects.all()[random.randint(1,15)]
ap , c = ArticlePublication.objects.get_or_create(article=article, publication=pub)
print 'Get or create', self.name
Вы обнаружите, что исключение больше не отображается. Не стесняйтесь увеличивать количество итераций. Я только поднялся до 1000 с get_or_create
, он не выбрал исключение. Однако add()
обычно генерирует исключение из 20 итераций.
Почему это работает?
Потому что get_or_create является атомарным.
Этот метод является атомарным, предполагающим правильное использование, правильную базу данных конфигурации и правильного поведения базовой базы данных. Однако, если уникальность не применяется на уровне базы данных для kwargs, используемые в вызове get_or_create (см. уникальный или unique_together), этот метод склонен к состоянию гонки, что может привести к множественным строки с теми же параметрами, которые вставлены одновременно.
Update:
Спасибо @louis за указание, что сквозная модель действительно может быть устранена. Thuse get_or_create
в MyThread2
можно изменить как.
ap , c = article.publications.through.objects.get_or_create(
article=article, publication=pub)