Как заблокировать доступ на чтение при создании модели в django?

Мы заметили много проблем в нашем приложении django, где мы используем get_or_create.

model, created = SomeModel.objects.get_or_create(user=user, name=name, defaults={...});
if created:
    get_some_dimensions # SLOW as we call to third party
    model.dimension_values.set(created_dimensions)

# Code here that depends on dimensions being there for the model

Проблема в том, что приведенный выше код выполняется дважды в одно и то же время.

  1. При первом создании модели он запускает «get_some_dimensions», но не завершает его (поэтому не сохраняется)
  2. потом запускается второй раз и видит, что модель уже есть
  3. однако второй запуск переходит к «cod here that depends on dimensions» до того, как первый запуск сохраняется.

На шаге 3 база данных переходит в ошибочное состояние.

Как я могу предотвратить реальный запуск второго запуска, заблокировать базу данных, пока не будет построен полный объект?

Завернуть в транзакцию?

with transaction.atomic():

    model, created = SomeModel.objects.get_or_create(user=user, name=name, defaults={...});
    if created:
        get_some_dimensions # SLOW as we call to third party
        model.dimension_values.set(created_dimensions)        
        # model.save() if .set doesn't do that

# the object doesn't exist until here where the transaction completes (exit with block)

Но я не специалист по БД, поэтому не уверен, что произойдет, если во время медленной работы третьей стороны произойдет другая get_or_create. Задержит ли база данных вторую операцию до завершения транзакции? Может ли это быть расценено пользователем как ошибка, если запрос третьей стороны может занимать десятки секунд?

Другой способ - проверить, выполняется ли SomeModel.objects.filter(...).exists(), и, если нет, запросить информацию у медленной третьей стороны перед выполнением get_or_create (предоставляя информацию третьей стороны в этот вызов, чтобы объект создавался в полном состоянии). Единственным недостатком здесь является возможный дублирующий запрос к третьей стороне за информацией, которая не нужна. (Стоит ли это больших денег за каждый вызов? )

Попробуйте использовать select_for_update с transaction.atomic. Он будет блокировать строки до тех пор, пока транзакция не будет зафиксирована, т. е. если есть код, который попытается получить заблокированную модель, он будет ждать, пока эта блокировка не будет освобождена. Пример:

with transaction.atomic():
    model, created = SomeModel.objects.get_or_create(user=user, name=name, defaults={...})
    # At this point, the selected row is locked.
    if created:
        get_some_dimensions # SLOW as we call to third party
        model.dimension_values.set(created_dimensions)
    # Once this block completes and commits, the lock is released.

Хотя вы можете настроить поведение ожидающего кода с помощью параметров skip_locked или no_wait.

Вернуться на верх