使用事务从数据库中安全删除Django模型

use*_*478 0 database django postgresql transactions django-models

在我的Django应用程序中,我具有从数据库中删除模型的单个实例的代码。两个并发请求都可能同时尝试删除同一模型。在这种情况下,我希望一个请求成功而另一个请求失败。我怎样才能做到这一点?

问题在于,当使用删除实例时delete(),Django不会返回有关命令是否成功的任何信息。此代码说明了问题:

b0 = Book.objects.get(id=1)
b1 = Book.objects.get(id=1)
b0.delete()
b1.delete()
Run Code Online (Sandbox Code Playgroud)

这两个delete()命令中只有一个实际上删除了该对象,但是我不知道哪个。不会引发任何异常,也不会返回任何指示命令成功的信息。在纯SQL中,该命令将返回删除的行数,如果该值为0,我知道删除失败。

我将PostgreSQL与默认的Read Commited隔离级别配合使用。我对此级别的理解是,每个命令(SELECT,DELETE等)都可以看到数据库的快照,但是下一个命令可以看到数据库的不同快照。我相信这意味着我不能做这样的事情:

# I believe this wont work
@commit_on_success
def view(request):
  try:
    book = Book.objects.get(id=1)
    # Possibility that the instance is deleted by the other request
    # before we get to the next delete()
    book.delete()
  except ObjectDoesntExist:
    # Already been deleted
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?

tee*_*ark 5

您可以使用QuerySet.delete而不是Model.delete将约束直接放入SQL DELETE语句中:

Book.objects.filter(pk=1).delete()
Run Code Online (Sandbox Code Playgroud)

这根本不会发出SELECT查询,只是以下方面:

DELETE FROM Book WHERE id=1;
Run Code Online (Sandbox Code Playgroud)

这样可以处理两个并发请求同时删除同一条记录的竞争状况,但是并不能让您知道删除是否先到达了那里。为此,您必须获取原始光标(django允许您执行此操作),.execute()上面的DELETE自己,然后获取光标的rowcount属性,如果您不打算删除任何内容,则该值为0。