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

Ber*_*Ber 77 django concurrency transactions atomic django-models

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

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

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

And*_*avu 47

这就是我在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()
Run Code Online (Sandbox Code Playgroud)

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

我做了以下假设:

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

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

警告 Django Doc:

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

  • 太好了!不应该是"&"而不是"&&"吗? (11认同)
  • @totowtwo ACID 中的 I 保证顺序(http://en.wikipedia.org/wiki/ACID)。如果对与并发(但稍后启动)SELECT 相关的数据执行 UPDATE,它将阻塞,直到 UPDATE 完成。但是可以同时执行多个 SELECT。 (2认同)

giZ*_*Zm0 36

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

select_for_update(nowait=True)
Run Code Online (Sandbox Code Playgroud)

文档

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

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

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

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

Gui*_*ume 28

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

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

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

  1. 读取数据并将其显示给用户
  2. 用户修改数据
  3. 用户发布数据
  4. 该应用程序将其保存回数据库.

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

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

UPDATE ... WHERE version = 'version_from_user';
Run Code Online (Sandbox Code Playgroud)

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

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

kra*_*etz 13

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让我静静地跳过复制触发器.


Ste*_*aug -3

为了安全起见,数据库需要支持事务

如果字段是“自由格式”,例如文本等,并且您需要允许多个用户能够编辑相同的字段(您不能拥有数据的单个用户所有权),您可以将原始数据存储在多变的。当用户提交时,检查输入数据与原始数据相比是否发生变化(如果没有,则无需通过重写旧数据来打扰数据库),原始数据与数据库中当前数据是否相同您可以保存,如果它发生了变化,您可以向用户展示差异并询问用户该怎么做。

如果字段是数字,例如帐户余额、商店中的商品数量等,如果您计算原始值(在用户开始填写表单时存储)和新值之间的差值,则可以更自动地处理它启动事务读取当前值并添加差值,然后结束事务。如果不能有负值,则应在结果为负时中止事务,并告诉用户。

我不了解 django,所以我不能给你 cod3s..;)