2015-10-05 110 views
2

我有一个领域的Django模型定义:检查约束

contacts = models.PositiveIntegerField(default=0)

... 再往下我试图做使用F()表达式领域的减量:

self.contacts = models.F("contacts") - quantity

如何查看contacts - quantity不引入竞争条件不变成负?

+0

AFAIK没有定义这种约束的方式,但你可以做交易中的支票('@ transaction.atomic')。还有[此库](https://code.google.com/p/django-check-constraints/wiki/Features),但它确实很旧。 – Ivan

+0

@伊万,谢谢你。如果我使用'@ transaction.atomic'装饰器,那么我不需要使用'F()'表达式,对吧? –

+0

您将已经检索到所有内容,因此不会。 – Ivan

回答

1

你想避免竞争条件。对象只是这方面的第一步。

您想要在您的交易中执行的第一件事是在您正在更改的行上获取锁。这可以防止您读取写入数据库时​​过时的值。这与该行的更新,直到事务被提交或回滚,将锁从进一步更新该行来完成:

with transaction.atomic(): 
    obj.contacts = F('contacts') - quantity 

当你有锁,并做了更新,检查数据的完整性仍然完好无损(即联系人数量不低于0)。如果是,则继续,否则,通过引发异常回滚事务:

obj.refresh_from_db() 
if obj.contacts < 0: 
    raise ValueError("Capacity exceeded") 

如果有足够的剩余接触,你会在此时退出的atomic()块,则提交事务,以及其他请求可以获取锁并尝试更新该值。如果没有足够的联系人,事务将回滚,并且其他请求永远不会知道该值已更改,因为他们一直在等待获取锁定。

现在,把它放在一起:

from django.db import transaction 
from django.db.models import F 

def update_contacts(obj, quantity): 
    with transaction.atomic(): 
     obj.contacts = F('contacts') - quantity 
     obj.save() 
     obj.refresh_from_db() 
     if obj.contacts < 0: 
      raise ValueError("Not enough contacts.") 

(注:obj.refresh_from_db()要求1.8,否则只是使用MyModel.objects.get(pk=obj.pk)

+0

难道你不会以这种方式至少打3次?你实际上只需要两个 - 检索,并保存,如果所有检查都没问题。 – Ivan

+0

我假设你也检索'obj'? – Ivan

+0

如果2个请求在另一个更新它并获得锁之前同时检索该对象,则它们都可以单独通过检查。在两个请求中更新对象时,联系人数量可能会降至0以下。如果要避免所有竞争条件,则至少需要3次数据库匹配。 – knbk