UniqueConstraint и get_or_create не работают?

Я пытаюсь сделать потокобезопасную/конкурентную функцию создания для некоторого моэля, как я понял, должно работать следующее:

@transaction.atomic
def create_new_request(self, request: Request):
    item_id = 1
    item = Item.objects.get(id=item_id)
    print("creating", request.user)
    purchase_request, request_created = PurchaseRequest.objects.select_for_update().get_or_create(
        requester=request.user,
        status="draft",
    )
    purchase_request.save()
    print("get or created: ", purchase_request.id, request_created)

Затем я создал уникальное ограничение в мета (поскольку только «черновики» имеют это ограничение - один черновик для одного пользователя)

class PurchaseRequest(models.Model):
    class Meta:
        verbose_name = "Original Purchaserequest"
        verbose_name_plural = "Original Purchaserequests"
        constraints = [
            models.UniqueConstraint(
                fields=["requester", "status"], condition=Q(status="draft"), name="unique_draft_user"
            )
        ]

Однако в тот момент, когда я отправил сразу несколько запросов с фронтенда (используя promise.all для одновременной отправки многих запросов), я заметил, что приложение падает со следующими ошибками:

creating paulweijtens
creating paulweijtens
creating paulweijtens
get or created:  483 True
2024-12-08 22:21:27,732 ERROR root  duplicate key value violates unique constraint "unique_draft_user"
DETAIL:  Key (requester_id, status)=(224, draft) already exists.
Traceback (most recent call last):
  File "", line 916, in get_or_create
    return self.get(**kwargs), False
           ^^^^^^^^^^^^^^^^^^
  File "", line 637, in get
    raise self.model.DoesNotExist(
isp.procure.purchase.models.PurchaseRequest.DoesNotExist: PurchaseRequest matching query does not exist.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "", line 89, in _execute
    return self.cursor.execute(sql, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "unique_draft_user"
DETAIL:  Key (requester_id, status)=(224, draft) already exists.

Что здесь происходит? Сначала обработчик говорит, что get не существует, а потом «существует». Я предполагаю, что это связано с состоянием гонки 3 запросов в одно и то же время, однако каково решение?

Как показано на рисунке, я уже сделал функцию атомарной, и я уже сделал блокировку строки на select_for_update()

Вы не выбрали ни одной строки, чтобы заблокировать их.

Вы пытаетесь наложить блокировку на новый ряд (тот, который вы только что создали), а другие транзакции не пытаются получить к нему доступ, поэтому блокировка игнорируется.

Необходимо наложить блокировку на связанную модель. В данном случае это будет модель User из request.user.

Попробуйте что-нибудь вроде этого:

@transaction.atomic
def create_new_request(self, request: Request):

    user = User.objects.select_for_update().get(id=request.user.id)
    purchase_request, request_created = PurchaseRequest.objects.get_or_create(
        requester=user,
        status="draft",
    )
    print("get or created: ", purchase_request.id, request_created)

Теперь, когда другой запрос попытается создать новый PurchaseRequest, он попытается сначала получить доступ к user, но на нем стоит блокировка, поэтому он будет ждать, пока предыдущая транзакция не завершится и блокировка не будет освобождена

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