Подтвердить что ты не робот

Django: как сделать get_or_create() в потоковом режиме?

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

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

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

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

4b9b3361

Ответ 1

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

Да.

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

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

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

Ответ 2

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

Этот метод является атомарным, предполагающим правильное использование, правильную базу данных конфигурации и правильного поведения базовой базы данных. Однако, если уникальность не применяется на уровне базы данных для kwargs, используемые в вызове get_or_create (см. уникальный или 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 и верните копию в этом дело. Другими словами: обрабатывать атомарность в базе данных.

Ответ 3

попробуйте обработчик transaction.commit_on_success для вызываемого, где вы пытаетесь get_or_create (** kwargs)

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

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

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