Django中的多道程序设计,写入数据库

时间:2010-03-12 16:39:34

标签: django django-models

简介

我有以下代码检查数据库中是否存在类似的模型,如果不存在则创建新模型:

class BookProfile():

    # ...

    def save(self, *args, **kwargs):

        uniqueConstraint = {'book_instance': self.book_instance, 'collection': self.collection}

        # Test for other objects with identical values
        profiles = BookProfile.objects.filter(Q(**uniqueConstraint) & ~Q(pk=self.pk))

        # If none are found create the object, else fail.
        if len(profiles) == 0:
            super(BookProfile, self).save(*args, **kwargs)
        else:
            raise ValidationError('A Book Profile for that book instance in that collection already exists')

我首先构建我的约束,然后搜索具有我强制执行的值的模型必须是唯一的Q(**uniqueConstraint)。另外,我确保如果保存方法正在更新而不是插入,那么在查找 其他时,我们找不到 对象类似的对象~Q(pk=self.pk)

我应该提一下,我实现了软删除(使用经过修改的objects管理器,只显示未删除的对象)这就是为什么我必须检查自己而不是依赖unique_together错误。< / p>

问题

正确的是介绍的方式。我的问题是,当快速(或几乎同时)连续保存多个相同的对象时,有时两者都会被添加,即使第一个被添加也应该阻止第二个。

我已经测试了shell中的代码,每次运行它都会成功。因此,我的假设是,如果说我们有两个对象被添加了对象A和对象B.对象A在调用save()时运行其检查。然后,保存对象B的过程在处理器上获得一些时间。对象B运行相同的测试,但尚未添加对象A,因此将对象B添加到数据库中。然后,对象A重新获得对处理器的控制权,并且已经完成了测试,即使数据库中存在相同的对象B,它也会添加它。

我的想法

我担心涉及多道程序设计的原因是每个对象A和对象都是通过API保存视图添加的,因此每次保存都会对视图发出请求,因此单个请求对对象进行多次顺序保存。

可能是Apache正在为每个请求创建一个进程,从而导致我认为我看到的问题。正如您所料,问题有时只会发生,这是多道程序设计或多处理错误的特征。

如果是这种情况,有没有办法进行测试并将save()方法的部分设置为关键部分,以便在测试和集合之间不会发生进程切换?

3 个答案:

答案 0 :(得分:1)

根据您所描述的内容,假设多个Apache进程是问题的根源似乎是合理的。如果将Apache限制为单个工作进程,是否可以复制?

也许这个帖子中的建议会有所帮助:How to lock a critical section in Django?

另一种方法可能是利用队列。您只需将要保存的对象保存到队列中,并让另一个进程执行实际保存。这样,您可以保证按顺序处理对象。如果您的应用程序依赖于在返回响应时保存对象,那么这将无法正常工作,除非您还让请求进程等待结果(例如,查看已完成的队列)。

<强>更新 您可能会发现此信息很有用。 Dumpleton先生在制定考虑因素方面做得比我在这里总结的要好得多:

http://code.google.com/p/modwsgi/wiki/ProcessesAndThreading

http://code.google.com/p/modwsgi/wiki/ConfigurationGuidelines,尤其是定义流程组部分。

http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide 授予守护程序进程部分

http://code.google.com/p/modwsgi/wiki/IntegrationWithDjango 在页面底部找到以下文开头的文本部分:

  

现在,关于传统智慧   Django应该如此   优选仅用于单个   线程服务器。这意味着   Apache使用单线程   在UNIX系统上运行'prefork'MPM   避免多线程'工人'   MPM。

并阅读直到页面结尾。

答案 1 :(得分:1)

我找到了一个我认为可行的解决方案:

import threading

def save(self, *args, **kwargs):

    lock = threading.Lock()
    lock.acquire()
    try:
        # Test and Set Code
    finally:
        lock.release()

它不会破坏像that decorator这样的保存方法,到目前为止我还没有看到错误。

除非有人能说这不是一个正确的解决方案,否则我认为这是有效的。

更新

The accepted answer是这种变化的灵感来源。

我接触到的印象是,锁是某种特殊的巫术,可以通过正常的逻辑来豁免。这里每次都会运行lock = threading.Lock(),从而实例化一个新的解锁锁定,可以随时获取。

我需要一个单独的中央锁用于此目的,但是除非我一直在运行持有锁的线程,否则可能会发生这种情况?答案是将this answer中解释的文件锁用于接受的答案中提到的StackOverflow问题。

以下是针对我的情况修改的解决方案:

守则

以下是我修改后的DjangoLock。我希望保持相对于Django root的锁,为此,我将自定义变量放入settings.py文件中。

# locks.py

import os
import fcntl
from django.conf import settings

class DjangoLock:

    def __init__(self, filename):
        self.filename = os.path.join(settings.LOCK_DIR, filename)
        self.handle = open(self.filename, 'w')

    def acquire(self):
        fcntl.flock(self.handle, fcntl.LOCK_EX)

    def release(self):
        fcntl.flock(self.handle, fcntl.LOCK_UN)

    def __del__(self):
        self.handle.close()

现在有额外的LOCK_DIR设置变量:

# settings.py

import os
PATH = os.path.abspath(os.path.dirname(__file__))

# ...

LOCK_DIR = os.path.join(PATH, 'locks')

现在将把锁放在相对于Django项目根目录的名为locks的文件夹中。在我的情况下,确保你给apache写访问权限:

sudo chown www-data locks

最后用法和以前大致相同:

import locks

def save(self, *args, **kwargs):

    lock = locks.DjangoLock('ClassName')
    lock.acquire()
    try:
        # Test and Set Code
    finally:
        lock.release()

现在这是我正在使用的实现,并且接缝工作得非常好。感谢所有为此目的而做出贡献的人。

答案 2 :(得分:0)

您需要在save方法上使用同步。我还没有尝试过,但是here是一个可以用来做装饰的装饰。