我有以下代码检查数据库中是否存在类似的模型,如果不存在则创建新模型:
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()
方法的部分设置为关键部分,以便在测试和集合之间不会发生进程切换?
答案 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是一个可以用来做装饰的装饰。