Набор запросов для фильтрации ровно N объектов из таблицы
Я пытаюсь понять, как выделить ровно N доступных объектов таким образом, чтобы это было безопасно для одновременной работы. Есть ли способ написать кверисет, который не сможет вернуть хотя бы N объектов?
Это, вероятно, излишняя осторожность для моего конкретного приложения, поскольку шансы на гонку ничтожно малы, но мне интересно
То, что я знаю, не сработает
available = Foo.objects.filter( status=Foo.AVAILABLE).count()
if available < N:
#let the user know there aren't enough left
# but now something concurrent may grab them!
foo_qs = Foo.objects.select_for_update().filter(status=Foo.AVAILABLE)[:N]
with transaction.atomic():
for foo in foo_qs:
... # quite a bit. In RL I will have locked related objects as well.
foo.status=Foo.RESERVED
foo.save()
потому что нарезка кверисета гарантирует только не более N объектов.
Удаление среза может заблокировать большое количество строк, которые мне не нужны. Является ли это неэффективным? Вся таблица Foo не будет заблокирована надолго, потому что я обновляю только N объектов и затем выхожу из транзакции.
Является ли единственным решением захват объектов по одному за раз внутри транзакции, или получение всех объектов в виде списка и повторная проверка его длины?
with transaction.atomic():
foos = list( foo_qs)
if len(foos) < N:
raise ... # fail transaction
for foo in foos:
... # as before
Может быть, что-то вроде:
with transaction.atomic():
# Locks before checking the count
available = Foo.objects.select_for_update().filter(status=Foo.AVAILABLE)[:N]
if available.count() >= N:
available.update(status=Foo.RESERVED)
else:
raise ... # fail transaction
Используя list:
with transaction.atomic():
# Locks before checking the length
available = list(Foo.objects.select_for_update().filter(status=Foo.AVAILABLE)[:N])
if len(available) >= N:
for foo in available:
foo.status=Foo.RESERVED
foo.save()
else:
raise ... # fail transaction