2011-07-05 2 views
18

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

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

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

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

+1

Один из двух потоков получит дублируемую ошибку записи и исключение. Не будет дублированных данных. –

ответ

10

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

Да.

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

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

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

+0

Я испытал дубликаты записей в базе данных postgres, которые должны были быть уникальными, когда я использовал 'get_or_create' в методе представления, который получал параллельные запросы, я думаю, что это актуальная проблема. –

+1

@A Ли: с уникальными ограничениями индекса, которые были правильно определены, дубликат должен быть невозможен. Как вы могли обойти уникальное ограничение индекса? –

+0

А, это бы устранило проблему сейчас, когда я думаю об этом более четко. 'Get_or_create' использовал несколько полей, и я переместил его в другой путь выполнения вместо того, чтобы оставить его в представлении и добавив уникальное ограничение для нескольких полей модели. –

3

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

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

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

он похож на этот пост How do I deal with this race condition in django?

+1

Это на самом деле не нужно, см. Мои другие ответы, чтобы лучше справиться с этим. –

27

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

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

+2

Благодаря тому, кто голосовал за этот ответ, я добавил несколько примеров, чтобы понять его еще проще. –

Смежные вопросы