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
, но на нем стоит блокировка, поэтому он будет ждать, пока предыдущая транзакция не завершится и блокировка не будет освобождена