Django“模仿"批量插入/更新/删除数据库触发行为 [英] Django "emulate" database trigger behavior on bulk insert/update/delete

查看:324
本文介绍了Django“模仿"批量插入/更新/删除数据库触发行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个自我解释的问题,但是我们开始. 我正在Django中创建一个业务应用程序,但我不想在应用程序和数据库中散布"所有逻辑,但是另一方面,我不想让数据库处理此任务(可能通过触发器)的使用.

It's a self expaining question but here we go. I'm creating a business app in Django, and i didn't wanted to "spread" all the logic across app AND database, but in the other hand, i didn't wanted to let the Database handle this task (its possible through the use of Triggers).

所以我想重现" Databse触发器的行为,但是要在Django的Model类内部(目前使用Django 1.4).

So I wanted to "reproduce" the behavior of the Databse Triggers, but inside the Model Class in Django (um currently using Django 1.4).

经过研究,我发现使用单个对象可以覆盖"models.Model"类的"save"和"delete"方法,插入"before"和"after"钩子以便执行父母保存/删除之前和之后.像这样:

After some research, I figured out that with single objects, I could override the "save" and "delete" methods of "models.Model" class, inserting the "before" and "after" hooks so they could be executed before and after the parent's save/delete. Like This:

     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()

BIG问题与批量操作有关.当从QuerySet运行"update()"/"delete()"时,Django不会触发模型的保存/删除.插入后,它使用QuerySet自己的方法.更糟糕的是,它也不会触发任何信号.

The BIG problem is with bulk operations. Django doesn't triggers the save/delete of the models when running the "update()"/"delete()" from it's QuerySet. Insted, it uses the QuerySet's own method. And to get a little bit worst, it doesn't trigger any signal either.

只是更具体一点:视图内的模型加载是动态的,因此不可能定义特定于模型"的方式.在这种情况下,我应该创建一个Abstract Class并在那里进行处理.

Just to be a little more specific: the model loading inside the view is dynamic, so it's impossible to define a "model specific" way. In this case, I should create an Abstract Class and handle it there.

我最后的尝试是创建一个自定义管理器,并在此自定义管理器中重写更新方法,循环遍历查询集内的模型,并触发每个模型的"save()"(考虑上述实现) ,或信号"系统).它可以工作,但是会导致数据库过载"(假设有1万行的查询集正在更新).

My last attempt was to create a custom Manager, and in this custom manager, override the update method, looping over the models inside the queryset, and trigering the "save()" of each model (take in consideration the implementation above, or the "signals" system). It works, but results in a database "overload" (imagine a 10k rows queryset being updated).

推荐答案

有一些警告,您可以覆盖queryset的update方法来触发信号,同时仍然使用SQL UPDATE语句:

With a few caveats, you can override the queryset's update method to fire the signals, while still using an SQL UPDATE statement:

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方法将清除queryset对象的缓存.

I clone the current queryset (using self.all()), because the update method will clear the cache of the queryset object.

有些问题可能会或可能不会破坏您的代码.首先,它将引入竞争条件.您在pre_save信号的接收器中执行某些操作,基于的是更新数据库时可能不再准确的数据.

There are a few issues that may or may not break your code. First of all it will introduce a race condition. You do something in the pre_save signal's receivers based on data that may no longer be accurate when you update the database.

大型查询集可能还会出现一些严重的性能问题.与update方法不同,所有模型都必须加载到内存中,然后仍然需要执行信号.特别是如果信号本身必须与数据库进行交互,则性能可能会令人无法接受地变慢.并且与常规的pre_save信号不同,更改模型实例不会自动导致数据库被更新,因为模型实例不用于保存新数据.

There may also be some serious performance issues with large querysets. Unlike the update method, all models will have to be loaded into memory, and then the signals still need to be executed. Especially if the signals themselves have to interact with the database, performance can be unacceptably slow. And unlike the regular pre_save signal, changing the model instance will not automatically cause the database to be updated, as the model instance is not used to save the new data.

可能还有其他一些问题,在少数情况下会引起问题.

There are probably some more issues that will cause a problem in a few edge cases.

无论如何,如果您可以在不遇到一些严重问题的情况下处理这些问题,那么我认为这是解决此问题的最佳方法.在将模型加载到内存的同时,它产生的开销尽可能小,这对于正确执行各种信号非常必要.

Anyway, if you can handle these issues without having some serious problems, I think this is the best way to do this. It produces as little overhead as possible while still loading the models into memory, which is pretty much required to correctly execute the various signals.

这篇关于Django“模仿"批量插入/更新/删除数据库触发行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆