Django: как сделать get_or_create () потокобезопасным способом? - PullRequest
18 голосов
/ 05 июля 2011

В моем приложении Django очень часто мне нужно сделать что-то похожее на get_or_create(). Например.,

Пользователь отправляет тег. Нужно посмотреть, если этот тег уже находится в базе данных. Если нет, создайте для него новую запись. Если это просто обновить существующий запись.

Но, глядя в документ на get_or_create(), похоже, что это не потокобезопасно. Поток A проверяет и находит, что Запись X не существует. Затем поток B проверяет и обнаруживает, что запись X не существует. Теперь и нить A, и нить B создадут новую запись X.

Это должно быть очень распространенная ситуация. Как мне справиться с этим потокобезопасным способом?

Ответы [ 3 ]

32 голосов
/ 28 февраля 2014

Начиная с 2013 года, get_or_create является атомарным, поэтому он прекрасно обрабатывает параллелизм:

Этот метод атомарный, при условии правильного использования, правильной базы данных конфигурация и правильное поведение базовой базы данных. Однако, если уникальность не обеспечивается на уровне базы данных для kwargs, используемые в вызове get_or_create (см. unique или unique_together), этот метод склонен к состоянию гонки, которое может привести к множественным строки с одинаковыми параметрами вставляются одновременно.

Если вы используете MySQL, обязательно используйте изоляцию READ COMMITTED уровень, а не REPEATABLE READ (по умолчанию), в противном случае вы можете увидеть случаи, когда get_or_create вызовет IntegrityError, но объект не появится в последующем вызове get ().

От: https://docs.djangoproject.com/en/dev/ref/models/querysets/#get-or-create

Вот пример того, как вы могли бы сделать это:

Определить модель с уникальным = True:

class MyModel(models.Model):
    slug = models.SlugField(max_length=255, unique=True)
    name = models.CharField(max_length=255)

MyModel.objects.get_or_create(slug=<user_slug_here>, defaults={"name": <user_name_here>})

... или с помощью unique_togheter:

class MyModel(models.Model):
    prefix = models.CharField(max_length=3)
    slug = models.SlugField(max_length=255)
    name = models.CharField(max_length=255)

    class Meta:
        unique_together = ("prefix", "slug")

MyModel.objects.get_or_create(prefix=<user_prefix_here>, slug=<user_slug_here>, defaults={"name": <user_name_here>})

Обратите внимание на то, как неуникальные поля находятся в dict по умолчанию, а НЕ среди уникальных полей в get_or_create. Это обеспечит атомарность ваших творений.

Вот как это реализовано в Django: https://github.com/django/django/blob/fd60e6c8878986a102f0125d9cdf61c717605cf1/django/db/models/query.py#L466 - Попробуйте создать объект, перехватите возможную ошибку IntegrityError и в этом случае верните копию. Другими словами: обрабатывать атомарность в базе данных.

11 голосов
/ 05 июля 2011

Это должно быть очень распространенная ситуация.Как мне справиться с этим потокобезопасным способом?

Да.

«Стандартное» решение в SQL - просто попытаться создать запись.Если это работает, это хорошо.Продолжайте.

Если попытка создать запись получает «дублирующееся» исключение из СУБД, выполните SELECT и продолжайте.

Однако у Django есть слой ORM сэто собственный кеш.Таким образом, логика перевернута, чтобы заставить общий случай работать напрямую и быстро, и необычный случай (дубликат) вызывает редкое исключение.

3 голосов
/ 29 декабря 2012

попробуйте использовать декоратор транзакцииmit_on_success для вызова, когда вы пытаетесь получить get_or_create (** kwargs)

"Используйте декоратор commit_on_success, чтобы использовать одну транзакцию для всей работы, выполненной в функции. Если функция вернется успешно, Django будет фиксировать всю работу, выполненную в функции в этой точке. Если функция вызывает исключение, хотя , Джанго откатит транзакцию. "

кроме этого, при одновременных вызовах get_or_create оба потока пытаются получить объект с переданным ему аргументом (за исключением аргумента "defaults", который является диктом, используемым во время вызова create в случае, если get () не может получить какой-либо объект). в случае сбоя оба потока пытаются создать объект, в результате чего получается несколько дублированных объектов, если только некоторые уникальные / уникальные вместе не реализованы на уровне базы данных с полями, используемыми в вызове get ().

похоже на этот пост Как мне справиться с этим состоянием гонки в Джанго?

...