当前位置:  开发笔记 > 后端 > 正文

Django:我如何防止数据库条目的并发修改

如何解决《Django:我如何防止数据库条目的并发修改》经验,为你挑选了4个好方法。

如果有办法防止两个或多个用户同时修改同一数据库条目?

向执行第二次提交/保存操作的用户显示错误消息是可以接受的,但不应以静默方式覆盖数据.

我认为锁定条目不是一个选项,因为用户可能会使用"后退"按钮或只是关闭他的浏览器,永远保持锁定.



1> Andrei Savu..:

这就是我在Django中做乐观锁定的方法:

updated = Entry.objects.filter(Q(id=e.id) && Q(version=e.version))\
          .update(updated_field=new_value, version=e.version+1)
if not updated:
    raise ConcurrentModificationException()

上面列出的代码可以作为Custom Manager中的方法实现.

我做了以下假设:

filter().update()将导致单个数据库查询,因为过滤器是惰性的

数据库查询是原子的

这些假设足以确保之前没有其他人更新过该条目.如果以这种方式更新多行,则应使用事务.

警告 Django Doc:

请注意,update()方法直接转换为SQL语句.这是直接更新的批量操作.它不会在模型上运行任何save()方法,也不会发出pre_save或post_save信号


太好了!不应该是"&"而不是"&&"吗?

2> giZm0..:

这个问题有点陈旧,我的回答有点晚了,但据我所知,这已经在Django 1.4中修复了:

select_for_update(nowait=True)

看文档

返回一个查询集,该查询集将锁定行直到事务结束,在支持的数据库上生成SELECT ... FOR UPDATE SQL语句.

通常,如果另一个事务已经对其中一个选定行获取了锁定,则查询将阻塞,直到锁定被释放.如果这不是您想要的行为,请调用select_for_update(nowait = True).这将使呼叫无阻塞.如果另一个事务已经获取了冲突锁,则在评估查询集时将引发DatabaseError.

当然,这仅在后端支持"select for update"功能时才有效,例如sqlite不支持.不幸的是:nowait=TrueMySql不支持,你必须使用:nowait=False,它只会在锁被释放之前阻塞.


我喜欢这个答案,因为它是Django的文档而不是任何第三方的漂亮发明.
这不是一个很好的答案 - 这个问题显然不希望(悲观)锁定,而且两个更高投票的答案目前主要关注乐观并发控制("乐观锁定").但是,在其他情况下,选择更新很好.

3> Guillaume..:

实际上,事务在这里对你没什么帮助...除非你想让事务在多个HTTP请求上运行(你很可能不想要).

我们在这些情况下通常使用的是"乐观锁定".据我所知,Django ORM不支持这一点.但是有一些关于添加此功能的讨论.

所以你是独立的.基本上,您应该做的是在模型中添加"版本"字段,并将其作为隐藏字段传递给用户.更新的正常周期是:

    读取数据并将其显示给用户

    用户修改数据

    用户发布数据

    该应用程序将其保存回数据库.

要实现乐观锁定,在保存数据时,检查从用户获得的版本是否与数据库中的版本相同,然后更新数据库并增加版本.如果不是,则表示自加载数据后发生了更改.

您可以通过单个SQL调用执行此操作,例如:

UPDATE ... WHERE version = 'version_from_user';

仅当版本仍然相同时,此调用才会更新数据库.


另外请注意,你想在此之上使用事务,以避免出现这种情况:http://hardware.slashdot.org/comments.pl?sid=1381511&cid=29536613 Django提供的中间件中自动换行对数据库的每一个动作一个事务,从初始请求开始,只在成功响应后提交:http://docs.djangoproject.com/en/dev/topics/db/transactions/(请注意:事务中间件只能帮助避免上述问题乐观锁定,它本身不提供锁定)

4> kravietz..:

Django 1.11有三个方便的选项来处理这种情况,具体取决于您的业务逻辑要求:

Something.objects.select_for_update() 将阻止,直到模型变为空闲

Something.objects.select_for_update(nowait=True)并捕获DatabaseError模型当前是否已锁定以进行更新

Something.objects.select_for_update(skip_locked=True) 不会返回当前锁定的对象

在我的应用程序中,它在各种模型上同时具有交互式和批处理工作流程,我找到了这三个选项来解决大多数并发处理方案.

"等待" select_for_update在顺序批处理过程中非常方便 - 我希望它们都能执行,但让他们花时间.在nowait使用时用户要修改当前锁定用于更新的对象-我会告诉他们这是一个在这一刻被修改.

skip_locked用户何时可以触发对象的重新扫描是另一种类型的更新,有用的-我不关心谁触发它,只要它的触发,所以skip_locked让我静静地跳过复制触发器.

推荐阅读
围脖上的博博_771
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有