【Django 2021年最新版教程32】Django 事务 悲观锁 乐观锁
事务处理(transaction)对于Web应用开发至关重要, 它可以维护数据库的完整性, 使整个系统更加安全。比如用户A通过网络转账给用户B,数据库里A账户中的钱已经扣掉,而B账户在接收过程中服务器突然发生了宕机,这时数据库里的数据就不完整了。加入事务处理机制后,如果在一连续交易过程中发生任何意外, 程序将回滚,从而保证数据的完整性。本文将总结事务的四大特性以及Django项目开发中如何操作事务,并以实际代码演示悲观锁和乐观锁。
事务处理(transaction)对于Web应用开发至关重要, 它可以维护数据库的完整性, 使整个系统更加安全。比如用户A通过网络转账给用户B,数据库里A账户中的钱已经扣掉,而B账户在接收过程中服务器突然发生了宕机,这时数据库里的数据就不完整了。加入事务处理机制后,如果在一连续交易过程中发生任何意外, 程序将回滚,从而保证数据的完整性。本文将总结事务的四大特性以及Django项目开发中如何操作事务,并以实际代码演示悲观锁和乐观锁。
DATABASES = {'default': {'ENGINE': 'django.db.backends.mysql','NAME': 'db1','HOST': 'dbhost','PORT': '3306','USER': 'dbuser','PASSWORD': 'password',#全局开启事务,绑定的是http请求响应整个过程'ATOMIC_REQUESTS': True, }
它的工作原理是这样的:每当有请求过来时,Django会在调用视图方法前开启一个事务。如果完成了请求处理并正确返回了结果,Django就会提交该事务。否则,Django会回滚该事务。
如果你全局开启了事务,你仍然可以使用non_atomic_requests
装饰器让某些视图方法不受事务控制,如下所示:
from django.db import transaction@transaction.non_atomic_requests
def my_view(request):do_stuff()# 如有多个数据库,让使用otherdb的视图不受事务控制
@transaction.non_atomic_requests(using='otherdb')
def my_other_view(request):do_stuff_on_the_other_database()
虽然全局开启事务很简单,但Django并不推荐开启全局事务。因为一旦将事务跟 HTTP 请求绑定到一起时,每一个请求都会开启事务,当访问量增长到一定的时候会造成很大的性能损耗。在实际开发过程中,很多GET请求根本不涉及到事务操作,一个更好的方式是局部开启事务按需使用。
局部开启事务
Django项目中局部开启事务,可以借助于transaction.atomic
方法。使用它我们就可以创建一个具备原子性的代码块,一旦代码块正常运行完毕,所有的修改会被提交到数据库。反之,如果有异常,更改会被回滚。
atomic
经常被当做装饰器
来使用,如下所示:
# 案例一:函数视图from django.db import transaction@transaction.atomicdef viewfunc(request):# This code executes inside a transaction.do_stuff()# 案例二:基于类的视图from django.db import transactionfrom rest_framework.views import APIViewclass OrderAPIView(APIView):# 开启事务,当方法执行完以后,自动提交事务@transaction.atomic def post(self, request):pass
使用了atomic
装饰器,整个视图方法里的代码块都会包裹着一个事务中运行。有时我们希望只对视图方法里一小段代码使用事务,这时可以使用transaction.atomic()
显式地开启事务,如下所示:
from django.db import transactiondef viewfunc(request):# 默认自动提交do_stuff()# 显式地开启事务with transaction.atomic():# 下面这段代码在事务中执行do_more_stuff()
Savepoint回滚
在事务操作中,我们还会经常显式地设置保存点(savepoint)。一旦发生异常或错误,我们使用savepoint_rollback方法让程序回滚到指定的保存点。如果没有问题,就使用savepoint_commit方法提交事务。示例代码如下:
from django.db import transactiondef viewfunc(request):# 默认自动提交do_stuff()# 显式地开启事务with transaction.atomic():# 创建事务保存点sid = transaction.savepoint()try:do_more_stuff()except Exception as e:# 如发生异常,回滚到指定地方。transaction.savepoint_rollback(sid) # 如果没有异常,显式地提交一次事务transaction.savepoint_commit(sid)return HttpResponse("Success")
注意:虽然SQLite支持保存点,但是sqlite3
模块设计中的缺陷使它们很难使用。
事务提交后回调函数
有的时候我们希望当前事务提交后立即执行额外的任务,比如客户下订单后立即邮件通知卖家,这时可以使用Django提供的on_commit
方法,如下所示:
# 例1from django.db import transactiondef do_something():pass # send a mail, invalidate a cache, fire off a Celery task, etc.transaction.on_commit(do_something)# 例2:调用celery异步任务transaction.on_commit(lambda: some_celery_task.delay('arg1'))
悲观锁与乐观锁
在电商秒杀等高并发场景中,仅仅开启事务还是无法避免数据冲突。比如用户A和用户B获取某一商品的库存并尝试对其修改,A, B查询的商品库存都为5件,结果A下单5件,B也下单5件,这就出现问题了。解决方案就是操作( 查询或修改)某个商品库存信息时对其加锁。
常见的锁有悲观锁和乐观锁,接下来我们来看下在Django项目中如何通过代码实现:
悲观锁就是在操作数据时,假定此操作会出现数据冲突,在整个数据处理过程中,使数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制实现的。
乐观锁是指操作数据库时想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理, 而在进行更新时,再去判断是否有冲突了。乐观锁不是数据库提供的锁,需要我们自己去实现。
Django实现悲观锁
Django中使用悲观锁锁定一个对象,需要使用select_for_update()
方法。它本质是一个行级锁,能锁定所有匹配的行,直到事务结束。两个应用示例如下所示:
# 案例1:类视图,锁定id=10的SKU对象class OrderView(APIView):@transaction.atomicdef post(self, request):# select_for_update表示锁,只有获取到锁才会执行查询,否则阻塞等待。sku = GoodsSKU.objects.select_for_update().get(id=10)# 等事务提交后,会自动释放锁。return Response("xxx")# 案例2:函数视图,锁定所有符合条件的文章对象列表。from django.db import transactionwith transaction.atomic():entries = Entry.objects.select_for_update().filter(author=request.user)for entry in entries:...
一般情况下如果其他事务锁定了相关行,那么本次查询将被阻塞,直到锁被释放。如果不想要使查询阻塞的话,使用select_for_update(nowait=True)
。
当你同时使用select_for_update
与select_related
方法时,select_related
指定的相关对象也会被锁定。你可以通过select_for_update(of=(...))
方法指定需要锁定的关联对象,如下所示:
# 只会锁定entry(self)和category,不会锁定作者authorentries = Entry.objects.select_related('author', 'category'). select_for_update(of=('self', 'category'))
注意:
select_for_update
方法必须与事务(transaction)同时使用。MySQL版本要在8.0.1+ 以上才支持
nowait
和of
选项。
Django实现乐观锁
乐观锁实现一般使用记录版本号,为数据表增加一个版本标识(version)字段,每次对数据的更新操作成功后都对版本号执行+1操作。每次执行更新操作时都去判断当前版本号是不是该条数据的最新版本号,如果不是说明数据已经同时被修改过了,则丢弃更新,需要重新获取目标对象再进行更新。
Django项目中实现乐观锁可以借助于django-concurrency
这个第三方库, 它可以给模型增加一个version
字段,每次执行save操作时会自动给版本号+1。
from django.db import modelsfrom concurrency.fields import IntegerVersionFieldclass ConcurrentModel( models.Model ):version = IntegerVersionField( )name = models.CharField(max_length=100)
下例中a和b同时获取了pk=1的模型对象信息,并尝试对其name字段进行修改。由于a.save()方法调用成功以后对象的版本号version已经加1,b再调用b.save()方法时将会报RecordModifiedError
的错误,这样避免了a,b同时修改同一对象信息造成数据冲突。
a = ConcurrentModel.objects.get(pk=1)a.name = '1'b = ConcurrentModel.objects.get(pk=1)b.name = '2'a.save()b.save()
那么问题来了,什么时候该用悲观锁,什么时候该用乐观锁呢?这主要需要考虑4个因素:
并发量:如果并发量不大且不允许脏读,可以使用悲观锁解决并发问题;但如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题, 建议乐观锁。
响应速度:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就失败,不需要等待其他并发去释放锁。乐观锁并未真正加锁,效率高。
冲突频率:如果冲突频率非常高,建议采用悲观锁,保证成功率。冲突频率大,选择乐观锁会需要多次重试才能成功,代价比较大。
重试代价:如果重试代价大,建议采用悲观锁。悲观锁依赖数据库锁,效率低。更新失败的概率比较低。
原文:https://mp.weixin.qq.com/s/lWvmSMibS9l_GjsWgVQW1A
【Django 2021年最新版教程32】Django 事务 悲观锁 乐观锁相关推荐
- 关于部署Django到阿里云服务器教程
部署Django到阿里云服务器教程 基于Ubuntu16.04 + Python3 + nginx + mysql + Django 欢迎访问我自己的博客网站:www.fengwanqing.xin ...
- python动态网页开发教程_python django创建一个属于自己的动态网站
您如何开始使用Python创建网站?好吧,你可以自己完成所有工作,并编写一个在Web服务器上运行的程序,接受页面请求并以HTML和其他资源的形式提供响应.然而,这是很多工作,那么为什么在有大量现有工具 ...
- Django 3.2.5博客开发教程:体验django模板
上面我们有说过,用户发送请求的时候,视图会返回一个响应,响应可以是一个重定向,一个404错误,一个XML文档,一张图片或者是一个HTML内容的网页.前面几个返回的信息比较有限,我们重点更多是放在HTM ...
- django一个html先后两个form,Django教程(三)- Django表单Form
目录: 1.Form 基本使用 django中的Form组件有以下几个功能: 生成HTML标签 验证用户数据(显示错误信息) HTML Form提交保留上次提交数据 初始化页面显示内容 2.Form中 ...
- Django个人博客搭建教程---用Vue写你的第一个前后端分离页面
一.构建Vue.js前端项目 npm install vue-cli -g npm install webpack -g 在项目根目录下(和你的app目录平级) vue init webpack my ...
- django学习-武沛齐教程【day4】
管理员操作 管理员账户 models.py class Admin(models.Model):username = models.CharField(verbose_name="用户名&q ...
- python setting.py_python基础教程:Django框架的中的setting.py文件说明详解
这篇文章主要介绍了Django框架的中的setting.py文件说明详解,这个文件包含了所有有关这个Django项目的配置信息,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 1.加载数据库,数据库 ...
- mysql注释符号_MySQL基础知识(2021最新版教程)
一.MySQL简介 MySQL是一种开放源代码的关系型数据库管理系统,使用最常用的数据库管理语言--结构化查询语言(SQL)进行数据库管理. MySQL是开放源代码的,因此任何人都可以在General ...
- cacti php zombie,Cacti1.2.x新版教程之监控本机
Cacti1.2.x新版教程之监控本机(吴昊博客独家首发)(二) 本文最后修改时间2021.03.17 上一篇文章介绍了Cacti1.2.2新版安装部署流程,本文继续来说下cacti监控. 本篇教程基 ...
- Django介绍和虚拟环境(django特点、MVC、MVT、Django学习资料)
MVT流程: 创建Django项目和应用 django-admin startproject name python manager.py startapp name 视图和ULR 视图的请求和响应 ...
最新文章
- 微软BI 之SSAS 系列 - 多维数据集维度用法之二 事实维度(退化维度 Degenerate Dimension)...
- 一个小的日常实践——距离阵列
- 清理apache共享内存引起的oracle宕机
- 【资源分享】CS起源 V34.4044(经典版本)
- SAP external long material id的奥妙
- (原创)c#学习笔记06--函数02--变量的作用域01--其他结构中变量的作用域
- 利用可视化软件navicat查看表的sql语句
- leetCode 206. Reverse Linked List 反转链表
- 九度OJ 1435:迷瘴
- javascript实现数组深复制的方法
- python未定义名称大小_名称错误:未定义名称“高度”
- jQuery文档操作常用方法1
- 填充图案的边界线 插件_cad如何给填充图案添加边界线
- 算法竞赛入门经典--大整数类
- 通过玩游戏从计算机小白到黑客的进阶之路!
- openwrt MT7621 支持512M内存
- cas 4.0单点登录服务端部署
- matlab图例使用技巧
- 共享单车原理大揭秘:小编亲自示范如何“撬锁”
- MatalbSimulink Control Tutorials笔记4-根轨迹设计控制器