如何在Django模型中管理并发性 orm select_for_update
如何在Django模型中管理并发性

为单用户服务的桌面系统的日子已经过去了 - 网络应用程序现在正在为数百万用户提供服务,许多用户出现了广泛的新问题 - 并发问题。
在本文中,我将介绍在Django模型中管理并发性的两种方法
问题
为了演示常见的并发问题,我们将使用银行账户模型:
开始我们为帐户实例提供一个简单的存款和撤销方法:
这似乎是足够简单的,甚至可能通过本地主机的单元测试和集成测试。 但是, 当两个用户同时在同一个帐户上执行操作时会发生什么?
1、用户A提取帐户 - 余额为100 $。
2、用户B提取帐户 - 余额为100 $。
3、用户B退出30 $ - 余额更新为100 $ - 30 $ = 70 $。
4、用户A存款50 $ - 余额更新为100 $ + 50 $ = 150 $。
这里发生了什么?
用户B要求提取30 $,用户A存入50 $ - 我们预期余额为120 $,但最终为150 $。
为什么会这样呢?
在步骤4,当用户A更新余额时,他在存储器中存储的金额已经过时(用户B已经退出30 $)。
为了防止这种情况发生,我们需要确保我们正在处理的资源在我们正在计算的过程中不会改变。
悲观的做法
悲观的做法表明,您应该完全锁定资源,直到完成它 。 如果没有人可以在您处理对象时获取对象上的锁定,那么可以确保对象没有被更改。
我们使用数据库锁有几个原因:
1、 数据库非常擅长管理锁并保持一致性。
2、数据库是访问数据的最低级别 - 获取最低级别的锁也会防止其他进程尝试修改数据。 例如,DB中的直接更新,cron作业,清理任务等。
3、Django应用程序可以在多个进程 (例如工作者)上运行。 在应用程序级别维护锁将需要大量(不必要的)工作。
要在Django中锁定一个对象,我们使用select_for_update 。
让我们用悲观的方法来实行安全的存款和取款:
按以下步骤:
1、我们在我们的查询器上使用select_for_update来告诉数据库锁定对象,直到事务完成。
2、在数据库中锁定一行需要一个数据库事务 - 我们使用Django的装饰器transaction.atomic来定义事务。
3、我们使用类方法而不是实例方法 - 我们告诉数据库要上锁,然后它会返回锁的对象给我们。 为了实现这一点,我们需要从数据库中获取对象。 如果我们使用self,那么就是在操作一个已经从数据库中获取出来的对象,这个对象无法保证自己是没有被上锁的。
4、帐户中的所有操作都在数据库事务中执行。
让我们看看如何通过我们的新方法来阻止前面说的情况:
1、用户A要求退出30 $:
- 用户A获取帐户上的锁。
-余额为100美元。
2、用户B要求存入50 $:
- 尝试获取锁定帐户失败(由用户A锁定)。
- 用户B等待锁释放 。
3、用户A撤回30 $:
- 余额是70 $。
- 帐户上的用户A的锁定被释放 。
4、用户B获取帐户上的锁。
-余额是70 $。
- 新余额为70 $ + 50 $ = 120 $。
5、账号上用户B的锁定被释放,余额为120 $。
Bug消失了!
这里你需要了解select_for_update
1、在我们的方案中,用户B等待用户A释放锁,我们可以告诉Django 不要等待锁释放并引发DatabaseError。 为此,我们可以将select_for_update的nowait参数设置为True, …select_for_update(nowait=True) 。
2、选择相关对象也被锁定 -当使用select_for_update与select_related时,相关对象也被锁定。
例如,如果我们选择与用户一起select_related帐户,用户和帐户将被锁定。 如果在存款期间,例如有人正在尝试更新名字,该更新将失败,因为用户对象被锁定。
如果您正在使用PostgreSQL或Oracle,这可能不是一个问题,由于即将到来的Django 2.0 的新功能 。 在此版本中,select_for_update具有“of”选项,用于显式地声明要锁定查询中的哪些表 。
我用过去的银行账户示例来展示我们在Django模型中使用的常见模式,欢迎您在本下文中跟进:
https://medium.com/@hakibenita/bullet-proofing-django-models-c080739be4e
乐观的方法
与悲观的方法不同,乐观的方法不需要锁定对象。 乐观的方法假定冲突不是很常见 ,并且指出只应确保在更新时对对象没有做任何更改。
我们如何用Django来实现这样的事情?
首先,我们添加一列以跟踪对该对象所做的更改:
然后,当我们更新一个对象时,我们确保版本没有改变:
接着:
1、我们直接在实例上操作(没有类方法)。
2、我们依赖于每次更新对象时增加版本的事实。
3、仅当版本没有更改时,我们才会进行更新:
- 如果对象没有被修改,我们获取它,而不是对象被更新 。
- 如果被修改 ,则查询将返回零记录,并且对象不会被更新 。
4、Django返回更新行数。 如果“更新”为零,则表示有人在我们获取对象之后更改了对象。
乐观锁定在我们的场景中如何工作:
1. 用户A获取帐户 - 余额为100 $,版本为0。
2. 用户B提取帐户 - 余额为100 $,版本为0。
3. 用户B要求退出30 $:
- 余额更新为100 $ - 30 $ = 70 $。
- 版本增加到1。
4. 用户A要求存入50 $:
- 计算余额为100 $ + 50 $ = 150 $。
- 该帐户不存在与版本0 - > 没有更新。
您需要了解乐观的方法:
不像悲观的方法,这种方法需要一个额外的空间和很多规则 。 克服纪律问题的一个方法是抽象这个行为。 django-fsm 使用如上所述的版本字段来实现乐观锁定 。 django-optimistic-lock似乎也是这样做的。 我们没有使用任何这些包,但灵感来自这里。
在具有大量并发更新的环境中,这种方法可能是浪费的。
这种方法不会对应用程序之外的对象进行修改。 如果您有直接修改数据的其他任务(例如,不通过模型),则需要确保它们也使用该版本。
使用乐观的方法, 函数可以失败并返回false。 在这种情况下,我们很可能想要重试操作。 使用悲观的方法与nowait = False操作不能失败 - 它将等待释放锁。
我应该使用哪一个?
像任何伟大的问题一样,答案取决于以下:
如果您的对象有很多并发更新,那么悲观的方式更好。
如果您在ORM之外发生更新(例如,直接在数据库中),则悲观的方法更安全。
如果您的方法具有远程API调用或操作系统调用等副作用,请确保它们是安全的。 还有些事情要考虑 - 远程通话可能需要很长时间吗? 远程电话是否正常(重试安全)?
如何在Django模型中管理并发性 orm select_for_update的更多相关文章
- Django模型中OneToOneField和ForeignKey的区别
网上看到一篇讲解"Django模型中OneToOneField和ForeignKey区别" 的文章,浅显易懂; 可以把ForeignKey形象的类比为: ForeignKey是on ...
- 如何让django模型中的字段和model名显示为中文
如何让django模型中的字段和model名显示为中文:在模型中加入class Meta即可 class People(models.Model): name = models.CharField(n ...
- Django中管理并发操作
上一篇我们说了,如何在Django中进行事务操作,数据的原子性操作 涉及了事务操作,我们不得不考虑的另一个问题就是:并发操作 还是那个用户转账的操作 我们使用事务操作解决的操作中途服务器宕机问题 但是 ...
- django模型中的抽象类(abstract)
首先介绍下django的模型有哪些属性:先看例子: Django 模型类的Meta是一个内部类,它用于定义一些Django模型类的行为特性.以下对此作一总结: abstract 这个属性是定义当前的模 ...
- Django 模型中自定义Manager和模型方法
1.自定义管理器(Manager) 在语句Book.objects.all()中,objects是一个特殊的属性,通过它来查询数据库,它就是模型的一个Manager. 每个Django模型至少有一个m ...
- django模型中的关系对应
显然,关系数据库的力量在于将表相互关联.Django提供了定义三种最常见的数据库关系类型的方法:多对一,多对多和一对一. 在说明之前,首先来理解一下这三个概念: 多对一: 两个集合a,b;集合a中的多 ...
- django 模型中 class Meta 内 各种属性的用法
Django 模型类的Meta是一个内部类,它用于定义一些Django模型类的行为特性.下面对此作一总结: abstract 这个属性是定义当前的模型类是不是一个抽象类.所谓抽象类是不会相应数据库表的 ...
- Flume-NG中Transaction并发性探究
我们曾经在Flume-NG中的Channel与Transaction关系(原创)这篇文章中说了channel和Transaction的关系,但是在source和sink中都会使用Transaction ...
- Django 模型中FileField字段
FileField¶ class FileField([upload_to=None, max_length=100, **options])¶ 一个上传文件的字段. 注意 FileField字段不支 ...
随机推荐
- ①HttpURLConnection通过报文提交
在进行短信发送的接口,因厂家不同,有的厂家会采用报文的格式进行短信请求的发送与接收.本文主要介绍利用HttpURLConnection进行短信报文的请求与响应. 一般的url请求分为两种,一种是GET ...
- (转)Android高性能编程(1)--基础篇
关于专题 本专题将深入研究Android的高性能编程方面,其中涉及到的内容会有Android内存优化,算法优化,Android的界面优化,Android指令级优化,以及Android应用内存占 ...
- Winform 自定义TabControl实现浏览器标签
作者:Gavin(daisong.michelangelo@gmail.com) 时间: Nov, 2015 封面图片为Gavin原创,请勿未经允许私自引 最近因为工作需要,要做一个桌面浏览器,和大多 ...
- GOF23设计模式之单例模式(singleton)
一.单例模式概述 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点. 由于单例模式只生成一个实例,减少了系统性能开销.所以当一个对象的产生需要比较多的资源时,如读取配置.产生其他依赖对象时, ...
- zk 06之:ZooKeeper命令、命令行工具及简单操作
常用命令ZooKeeper 支持某些特定的四字命令字母与其的交互.它们大多是查询命令,用来获取 ZooKeeper 服务的当前状态及相关信息.用户在客户端可以通过 telnet 或 nc 向 ZooK ...
- 巧妙的运用group,count,order有利于统计
$aAwardMem = $this->dao_raward->getAwardAndMem($where,array('award_cat asc','award_level asc') ...
- Unity3D版本之我见
关心Unity版本的变化以及了解未来版本的内容是专业做Unity的同学必备的功课,下面我来说一下我对4.0以后版本的一些见解. v4.0: 这个版本比3.5有较大的跳跃,首先最大卖点是新的动作系统Me ...
- Py修行路 python基础 (二十二)异常处理
异常处理 一.错误和异常 程序中难免出现错误,而错误分为两种:语言异常和逻辑异常 1.语法错误(这种错误,根本过不了python解释器的语法检测,必须在程序执行前就改正) for i in range ...
- IDA Pro 权威指南学习笔记(二) - IDA 数据库文件
生成数据库文件 把要分析的文件用 IDA 打开后,会生成 3 个数据库文件 扩展名分别为 .id0,id1,nam .id0 文件是一个二叉树形式的数据库 .id1 文件包含描述每个程序字节的标记 . ...
- MFC 文档/视图
1.文档修改后,关闭时需要保存,主要用到2个函数,在需要更改文档内容的函数里调用SetModifiedFlag(TRUE),另一个就是SaveModified()函数,简单的例子: BOOL CMFC ...