此代码应该获取或创建一个对象,并在必要时更新它。该代码在网站上正在生产中使用。
在某些情况下 - 当数据库繁忙时 - 它将抛出异常“DoesNotExist:MyObj匹配查询不存在”。
# Model:
class MyObj(models.Model):
thing = models.ForeignKey(Thing)
owner = models.ForeignKey(User)
state = models.BooleanField()
class Meta:
unique_together = (('thing', 'owner'),)
# Update or create myobj
@transaction.commit_on_success
def create_or_update_myobj(owner, thing, state)
try:
myobj, created = MyObj.objects.get_or_create(owner=user,thing=thing)
except IntegrityError:
myobj = MyObj.objects.get(owner=user,thing=thing)
# Will sometimes throw "DoesNotExist: MyObj matching query does not exist"
myobj.state = state
myobj.save()
我在ubuntu上使用innodb mysql数据库。
我如何安全地处理这个问题?
答案 0 :(得分:41)
这可能是与此问题相同的问题:
Why doesn't this loop display an updated object count every five seconds?
基本上get_or_create 可能会失败 - 如果你看看它的来源,那么你会看到它是:get,if-problem:save + some_trickery,if-still-问题:再来一次,如果仍然有问题:投降并加注。
这意味着如果有两个同时运行create_or_update_myobj
的线程(或进程),两者都试图get_or_create同一个对象,那么:
unique
约束get
没有看到第一个线程中创建的对象所以,如果你想安全get_or_create
任何事情,请尝试这样的事情:
@transaction.commit_on_success
def my_get_or_create(...):
try:
obj = MyObj.objects.create(...)
except IntegrityError:
transaction.commit()
obj = MyObj.objects.get(...)
return obj
还有第二个解决问题的方法 - 使用READ COMMITED隔离级别,而不是REPEATABLE READ。但它的测试较少(至少在MySQL中),因此可能存在更多的错误/问题 - 但至少它允许将视图绑定到事务,而不是在中间进行。
以下是关于MySQL和Django的一些好的博客文章(不是我的),与这个问题相关:
http://www.no-ack.org/2010/07/mysql-transactions-and-django.html
http://www.no-ack.org/2011/05/broken-transaction-management-in-mysql.html
答案 1 :(得分:3)
您的异常处理掩盖了错误。您应该在state
中传递get_or_create()
的值,或在模型和数据库中设置默认值。
答案 2 :(得分:0)
一种(愚蠢的)方式可能是捕获错误,只需等待一小段时间后重试一次或两次。我不是数据库专家,所以可能有一个信令解决方案。
答案 3 :(得分:0)
自2012年以来,在Django中,我们有select_for_update
会锁定行直到交易结束。
为了避免Django + MySQL中的竞争条件 在默认情况下:
您可以使用此:
with transaction.atomic():
instance = YourModel.objects.select_for_update().get(id=42)
instance.evolve()
instance.save()
第二个线程将等待第一个线程(锁定),只有第一个线程完成后,第二个线程才会读取第一个线程保存的数据,因此它将在更新后的数据上工作。
然后与get_or_create
一起:
def select_for_update_or_create(...):
instance = YourModel.objects.filter(
...
).select_for_update().first()
if order is None:
instnace = YouModel.objects.create(...)
return instance
该函数必须在事务块内,否则,您将从Django获得: TransactionManagementError:select_for_update不能在事务之外使用
有时候使用refresh_from_db()
也很好
如果是这样的话:
instance = YourModel.objects.create(**kwargs)
response = do_request_which_lasts_few_seconds(instance)
instance.attr = response.something
您想看:
instance = MyModel.objects.create(**kwargs)
response = do_request_which_lasts_few_seconds(instance)
instance.refresh_from_db() # 3
instance.attr = response.something
#3会减少很多可能出现比赛状况的时间窗口,因此有机会。