如果有办法防止两个或多个用户同时修改同一数据库条目?
向执行第二次提交/保存操作的用户显示错误消息是可以接受的,但不应以静默方式覆盖数据.
我认为锁定条目不是一个选项,因为用户可能会使用"后退"按钮或只是关闭他的浏览器,永远保持锁定.
这就是我在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信号
这个问题有点陈旧,我的回答有点晚了,但据我所知,这已经在Django 1.4中修复了:
select_for_update(nowait=True)
看文档
返回一个查询集,该查询集将锁定行直到事务结束,在支持的数据库上生成SELECT ... FOR UPDATE SQL语句.
通常,如果另一个事务已经对其中一个选定行获取了锁定,则查询将阻塞,直到锁定被释放.如果这不是您想要的行为,请调用select_for_update(nowait = True).这将使呼叫无阻塞.如果另一个事务已经获取了冲突锁,则在评估查询集时将引发DatabaseError.
当然,这仅在后端支持"select for update"功能时才有效,例如sqlite不支持.不幸的是:nowait=True
MySql不支持,你必须使用:nowait=False
,它只会在锁被释放之前阻塞.
实际上,事务在这里对你没什么帮助...除非你想让事务在多个HTTP请求上运行(你很可能不想要).
我们在这些情况下通常使用的是"乐观锁定".据我所知,Django ORM不支持这一点.但是有一些关于添加此功能的讨论.
所以你是独立的.基本上,您应该做的是在模型中添加"版本"字段,并将其作为隐藏字段传递给用户.更新的正常周期是:
读取数据并将其显示给用户
用户修改数据
用户发布数据
该应用程序将其保存回数据库.
要实现乐观锁定,在保存数据时,检查从用户获得的版本是否与数据库中的版本相同,然后更新数据库并增加版本.如果不是,则表示自加载数据后发生了更改.
您可以通过单个SQL调用执行此操作,例如:
UPDATE ... WHERE version = 'version_from_user';
仅当版本仍然相同时,此调用才会更新数据库.
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
让我静静地跳过复制触发器.