我如何处理这个在Django的竞争条件?
这段代码应该得到或创build一个对象,并在必要时进行更新。 该代码正在生产中使用的网站上。
在某些情况下(当数据库正忙时),它会抛出exception“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数据库。
我如何安全地处理这个问题?
这可能是一个与这里相同的问题:
为什么这个循环不会每五秒钟显示一个更新的对象计数?
基本上get_or_create 可能会失败 – 如果你看看它的源代码,你会发现它是:get,if-problem:save + some_trickery,if-still-problem:get again,if-still-problem:surrender and raise 。
这意味着如果有两个同时运行的线程(或进程)运行create_or_update_myobj
,都尝试get_or_create同一个对象,则:
- 第一个线程试图得到它 – 但它还不存在,
- 所以,线程尝试创build它,但在创build对象之前…
- 第二个线程试图获得它 – 这显然失败了
- 现在,由于MySQLdb数据库连接的默认AUTOCOMMIT = OFF和REPEATABLE READ可序列化级别,两个线程都冻结了它们对MyObj表的视图。
- 随后,第一个线程创build它的对象,并优雅地返回它,但…
- …第二个线程不能创build任何东西,因为它会违反
unique
约束 - 有趣的是,后来
get
的第二个线程没有看到在第一个线程中创build的对象,由于MyObj表的冻结视图
所以,如果你想安全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
27/05/2010编辑
还有第二种解决方法 – 使用READ COMMITED隔离级别,而不是REPEATABLE READ。 但是它的testing(至less在MySQL中)还是比较less的,所以可能会有更多的bug /问题,但是至less它允许将事务视图绑定到事务中,而不需要在中间进行。
2012年1月22日编辑
这里有一些关于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
您的exception处理掩盖了错误。 您应该在get_or_create()
为state
传递一个值,或者在模型和数据库中设置一个默认值。
一个(愚蠢)的方式可能是捕获错误,并在等待一段时间后重试一次或两次。 我不是数据库专家,所以可能有一个信号解决scheme。