Какой самый эффективный способ проверки на наличие сирот при удалении объектов в Django?
Допустим, у меня есть модель с таблицей Things
и таблицей отношений между вещами, называемой ThingRelations
. Не должно быть возможности удалить Thing
, если есть ThingRelations
, которые указывают на него, и когда больше нет ThingRelations
, указывающих на данный Thing
, он должен быть удален. В настоящее время я пытаюсь реализовать это с помощью signals.post_delete
следующим образом:
from django.db import models
class ThingRelation(models.Model):
first_thing = models.ForeignKey('Thing', on_delete=models.PROTECT)
second_thing = models.ForeignKey('Thing', on_delete=models.PROTECT)
class Thing(models.Model):
name = CharField(max_length=260)
@receiver(models.signals.post_delete, sender=ThingRelation)
def check_relation_and_delete(sender, instance, *args, **kwargs):
for thing_id in [instance.first_thing, instance.second_thing]:
first_thing_match = ThingRelation.objects.filter(first_thing=thing_id).exists()
second_thing_match = ThingRelation.objects.filter(second_thing=thing_id).exists()
if not first_thing_match and not second_thing_match:
Thing.objects.get(pk=thing_id).delete()
Является ли это наиболее эффективным способом поиска и удаления осиротевших Things
? Я очень новичок в базах данных в целом, но не будет ли фильтрация (потенциально довольно большая) таблицы Things
четыре раза для каждого удаленного ThingRelation
медленной при удалении многих объектов одновременно? Есть ли какой-то SQL или функциональность Django, которая позволяет не запускать этот код для каждого объекта в массовой операции?
signals.py
не предназначены для массовых операций. Также их часто считают анти-паттерном, из-за сложности их отслеживания при попытке отладки некоторой логики.
Я бы посоветовал вам попробовать рутинный подход. Пример, который может быть полезен для вас:
def remove_orphaned_things():
orphaned_things = Thing.objects.filter(
Q(id__in=ThingRelation.objects.values_list('first_thing_id').filter(
first_thing_id=OuterRef('pk')
) | Q(id__in=ThingRelation.objects.values_list('second_thing_id').filter(
second_thing_id=OuterRef('pk')
)
).delete()
Эта функция удаляет все осиротевшие Thing
. Осталось только правильно ее вызвать.
Самый простой и понятный способ - организовать его в бесконечную while-true
рутину с некоторыми sleep
и запускать ее как демон. Например:
from datetime import time
from django.core.management import BaseCommand
# Also some import of `remove_orphaned_things`
class Command(BaseCommand):
def handle(self, *args, **options):
while True:
remove_orphaned_things()
time.sleep(300)
Таким образом, он будет выполняться почти раз в 5 минут. Его можно запускать с чем-то вроде supervisor
(лучше) или tmux
(хуже), чтобы вы были уверены, что он всегда выполняется.
Лучше использовать что-то вроде periodic
для оркестровки. Вот руководство по установке (вам также понадобится RabbitMQ) и небольшой пример того, как это организовать:
@dramatiq.actor(periodic=cron('*/5 * * * *'), max_retries=0)
def remove_orphaned_things():
# Exact the same code of `remove_orphaned_things`
Удачи!