以原子方式比较-交换 Django 中的模型字段
Posted
技术标签:
【中文标题】以原子方式比较-交换 Django 中的模型字段【英文标题】:Atomically Compare-Exchange a Model Field in Django 【发布时间】:2016-10-31 17:03:16 【问题描述】:如何以原子方式比较-交换-保存 Django Model
实例 Field
的值? (使用 PostgreSQL 作为数据库后端)。
一个示例用例是确保具有相似内容的多个帖子(例如,提交相同表单)仅生效一次,而不依赖于不安全且仅有时有效的客户端 javascript 或表单 UUID 的服务器端跟踪,这对恶意的多发帖子是不安全的。
例如:
def compare_exchange_save(model_object, field_name, comp, exch):
# How to implement?
....
from django.views.generic.edit import FormView
from django.db import transaction
from my_app.models import LicenseCode
class LicenseCodeFormView(FormView):
def post(self, request, ...):
# Get object matching code entered in form
license_code = LicenseCode.objects.get(...)
# Safely redeem the code exactly once
# No change is made in case of error
try:
with transaction.atomic()
if compare_exchange_save(license_code, 'was_redeemed', False, True):
# Deposit a license for the user with a 3rd party service. Raises an exception if it fails.
...
else:
# License code already redeemed, don't deposit another license.
pass
except:
# Handle exception
...
【问题讨论】:
【参考方案1】:您要查找的是 QuerySet 对象上的 update
函数。
根据值,您可以与 Case、When 对象进行比较 - 查看 conditional updates 上的文档 注意 该链接适用于 1.10 - Case/When 进入 1.8。
您可能还会发现使用F
来引用字段中的值的实用程序。
例如:
我需要更新我的模型 Model 中的值:
(Model.objects
.filter(id=my_id)
.update(field_to_be_updated=Case(
When(my_field=True, then=Value(get_new_license_string()),
default=Value(''),
output_field=models.CharField())))
如果您需要使用 F
对象,只需在更新表达式中的等号右侧引用它即可。
更新不需要使用transaction.atomic()
上下文管理器,但如果您需要执行任何其他数据库操作,您应该继续使用transaction.atomic()
包装该代码
编辑:
您可能还想使用查询集select_for_update
方法,该方法在查询集执行时实现行锁docs。
【讨论】:
这能保证是原子的吗?我在文档中没有看到。如果不是,那么在我的用例中,可能有两个不同的请求进入“存入许可证”部分 这是一个单一的数据库调用。它尽可能地具有原子性。 @DanielRoseman 哪个是原子的,或者不是?也就是说,假设一个 PostgreSQL 后端,是否有可能同时调用此方法的两个并发请求都成功更新值?还是保证最多一次会成功? 添加了更多关于在调用中使用锁的查询集方法的信息。 @theWanderer4865 谢谢,太好了!您添加到 select_for_update 的文档表明您建议的第一种方法也适用于原子比较交换功能,假设行在更新时被锁定,这似乎是假设它被包装在事务中的情况。 (见postgresql.org/docs/9.1/static/explicit-locking.html:“更新或删除行时自动获取特定行上的排他行级锁。该锁一直保持到事务提交或回滚”)以上是关于以原子方式比较-交换 Django 中的模型字段的主要内容,如果未能解决你的问题,请参考以下文章