Django在批量插入/更新/删除时“模拟”数据库触发器行为
这个问题自我解释得很清楚,但我还是来详细说一下。 我正在用Django创建一个商业应用,我不想把所有的逻辑分散在应用和数据库之间,但另一方面,我也不想让数据库来处理这些任务(其实可以通过使用触发器来实现)。
所以我想在Django的模型类中“重现”数据库触发器的行为(我现在使用的是Django 1.4)。
经过一些研究,我发现对于单个对象,我可以重写“models.Model”类的“save”和“delete”方法,插入“前”和“后”的钩子,这样就可以在父类的保存/删除之前和之后执行它们。像这样:
class MyModel(models.Model):
def __before(self):
pass
def __after(self):
pass
@commit_on_success #the decorator is only to ensure that everything occurs inside the same transaction
def save(self, *args, *kwargs):
self.__before()
super(MyModel,self).save(args, kwargs)
self.__after()
但大问题出在批量操作上。当我从QuerySet运行“update()”或“delete()”时,Django并不会触发模型的保存或删除。相反,它使用的是QuerySet自己的方法。而且更糟糕的是,它也不会触发任何信号。
编辑: 为了更具体一点:在视图中加载的模型是动态的,所以不可能定义一种“特定于模型”的方式。在这种情况下,我应该创建一个抽象类并在那里面处理。
我最后的尝试是创建一个自定义管理器,在这个自定义管理器中重写更新方法,循环遍历QuerySet中的模型,并触发每个模型的“save()”(考虑到上面的实现或“信号”系统)。这样做是可行的,但会导致数据库“过载”(想象一下更新一个有1万行的QuerySet)。
2 个回答
首先,不用重写保存方法来添加 __before
和 __after
方法,你可以使用内置的 pre_save
、post_save
、pre_delete
和 post_delete
信号。详细信息可以查看这个链接:https://docs.djangoproject.com/en/1.4/topics/signals/
from django.db.models.signals import post_save
class YourModel(models.Model):
pass
def after_save_your_model(sender, instance, **kwargs):
pass
# register the signal
post_save.connect(after_save_your_model, sender=YourModel, dispatch_uid=__file__)
当你在一个查询集上调用 delete()
时,pre_delete
和 post_delete
会被触发。
不过,对于批量更新,你需要手动调用你想要触发的函数。此外,你也可以把这些操作放在一个事务中。
如果你使用动态模型,可以通过检查模型的 ContentType 来调用正确的触发函数。例如:
from django.contrib.contenttypes.models import ContentType
def view(request, app, model_name, method):
...
model = get_model(app, model_name)
content_type = ContentType.objects.get_for_model(model)
if content_type == ContenType.objects.get_for_model(YourModel):
after_save_your_model(model)
elif content_type == Contentype.objects.get_for_model(AnotherModel):
another_trigger_function(model)
有一些注意事项,你可以重写查询集的 update
方法来触发信号,同时仍然使用 SQL 的 UPDATE
语句:
from django.db.models.signals import pre_save, post_save
def CustomQuerySet(QuerySet):
@commit_on_success
def update(self, **kwargs):
for instance in self:
pre_save.send(sender=instance.__class__, instance=instance, raw=False,
using=self.db, update_fields=kwargs.keys())
# use self instead of self.all() if you want to reload all data
# from the db for the post_save signal
result = super(CustomQuerySet, self.all()).update(**kwargs)
for instance in self:
post_save.send(sender=instance.__class__, instance=instance, created=False,
raw=False, using=self.db, update_fields=kwargs.keys())
return result
update.alters_data = True
我会克隆当前的查询集(使用 self.all()
),因为 update
方法会清空查询集对象的缓存。
这里有一些可能会导致代码出问题的情况。首先,它会引入一个竞争条件。你在 pre_save
信号的接收器中做的事情,可能会基于一些在更新数据库时不再准确的数据。
对于大型查询集,可能还会出现一些严重的性能问题。与 update
方法不同,所有模型都需要加载到内存中,然后信号还需要被执行。特别是如果信号本身需要与数据库交互,性能可能会变得非常慢。而且,与普通的 pre_save 信号不同,改变模型实例不会自动导致数据库更新,因为模型实例并没有用来保存新数据。
可能还有其他一些问题会在某些特殊情况下导致麻烦。
总之,如果你能处理这些问题而不出现严重的麻烦,我认为这是最好的做法。这样做的开销尽可能小,同时又能将模型加载到内存中,这对于正确执行各种信号是非常必要的。