10.Django之ORM中的锁和事务
锁
行锁
select_for_update(nowait=False, skip_locked=False) #注意必须用在事务里面
返回一个锁住行直到事务结束的查询集
entries = Entry.objects.select_for_update().filter(author=request.user)#加互斥锁,由于mysql在查询时自动加的是共享锁,所以我们可以手动加上互斥锁。create、update、delete操作时,mysql自动加行级互斥锁
所有匹配的行将被锁定,直到事务结束。这意味着可以通过锁防止数据被其它事务修改。
一般情况下如果其他事务锁定了相关行,那么本查询将被阻塞,直到锁被释放。 如果这不想要使查询阻塞的话,使用select_for_update(nowait=True)
事务
全局开启事务
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'mxshop', 'HOST': '127.0.0.1', 'PORT': '3306', 'USER': 'root', 'PASSWORD': 'root', 'OPTIONS': { "init_command": "SET default_storage_engine='INNODB'", #'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", #配置开启严格sql模式 } "ATOMIC_REQUESTS": True, #全局开启事务,绑定的是http请求响应整个过程 "AUTOCOMMIT":False, #全局取消自动提交,慎用 }, 'other':{ 'ENGINE': 'django.db.backends.mysql', ...... } #还可以配置其他数据库 }
如果想对某个http请求放水,可以用non_atomic_requests修饰器,那么他就不受事务的管控了
from django.db import transaction @transaction.non_atomic_requests def my_view(request): do_stuff() def my_other_view(request): do_stuff_on_the_other_database()
局部使用事务
给函数做装饰器来使用
from django.db import transaction @transaction.atomic def viewfunc(request): do_stuff()
作为上下文管理器来使用,其实就是设置事务的保存点
from django.db import transaction def viewfunc(request): do_stuff() with transaction.atomic(): #保存点 do_more_stuff() do_other_stuff()
一旦把atomic代码块放到try/except中,完整性错误就会被自然的处理掉了
from django.db import IntegrityError,transaction def viewfunc(request): create_parent() try: with transaction.atomic(): generate.relationships() except IntegrityError: handle_exception() add_children()
还可以嵌套使用,函数的事务嵌套上下文管理器的事务,上下文管理器的事务嵌套上下文管理器的事务等
from django.db import IntegrityError,transaction @transaction.atomic def viewfunc(request): create_parent() try: with transaction.atomic(): generate_relationships() except IntegrityError: handle_exception() add_children()
尽量不要在atomic代码块中捕获异常
因为当atomic块中的代码执行完的时候,Django会根据代码正常运行来执行相应的提交或者回滚操作。如果在atomic代码块里面捕捉并处理了异常,就有可能隐盖代码本身的错误,从而可能会有一些意料之外的不愉快事情发生。
担心主要集中在DatabaseError和它的子类(如IntegrityError)。如果这种异常真的发生了,事务就会被破坏掉,而Django会在代码运行完后执行回滚操作。如果你试图在回滚前执行一些数据库操作,Django会抛出TransactionManagementError。通常你会在一个ORM相关的信号处理器抛出异常时遇到这个行为。
捕获异常的正确方式正如上面atomic代码块所示。如果有必要,添加额外的atomic代码块来做这件事情,也就是事务嵌套。这么做的好处是:当异常发生时,它能明确地告诉你那些操作需要回滚,而那些是不需要的。
为了保证原子性,atomic还禁止了一些API。像试图提交、回滚事务,以及改变数据库连接的自动提交状态这些操 作,在atomic代码块中都是不予许的,否则就会抛出异常。
下面是Django的事务管理代码:
- 进入最外层atomic代码块时开启一个事务;
- 进入内部atomic代码块时创建保存点;
- 退出内部atomic时释放或回滚事务;注意如果有嵌套,内层的事务也是不会提交的,可以释放(正常结束)或者回滚
- 退出最外层atomic代码块时提交或者回滚事务;
注意:transaction只对数据库层的操作进行事务管理,不能理解为python操作的事务管理
from django.db import IntegrityError,transaction def example_view(request): tag = False with transaction.atomic(): tag = True chang_obj() obj.save() raise DaataError print('tag = ',tag)
transation的其他方法
from django.db import IntegrityError,transaction def viewfunc(request): a.save() sid = transaction.savepoint() #保存节点 b.save() if want_to_keep_b: transaction.savepoint_commit(sid) #提交保存点 else: transaction.savepoint_rollback(sid) #回滚保存点 transaction.commit() #手动提交事务,默认是自动提交的,也就是说如果你没有设置取消自动提交,那么这句话不用写,如果你配置了那个AUTOCOMMIT=False,那么就需要自己手动进行提交
为保证事务的隔离性,我们还可以结合上面的锁来实现,也就是说在事务里面的查询语句,咱们使用select_for_update显示的加锁方式来保证隔离性,事务结束后才会释放这个锁
from django.db import IntegrityError,transaction @transaction.atomic def handle(self): try: user = User.objects.select_for_update().get(open_id=self.user.open_id) except User.DoesNotExist: raise BaseError(-1,"User does not exist.")
通过Django外部的python脚本来测试一下事务
import os if __name__ == '__main__': os.environ.setdefault("DJANGO_SETTINGS_MODULE", "moxing.settings") import django django.setup() import datetime from app01 import models try: from django.db import transaction with transaction.atomic(): new_publisher = models.Publisher.objects.create(name='北京出版社') models.Book.objects.create(title='alex',publish_date = datetime.date.today(),publisher_id = 1) except Exception as e: print(str(e))
下面再说一些设置事务的小原则吧:
1.保持事务短小
2.尽量避免事务中rollback
3.尽量避免savepoint
4.默认情况下,依赖于悲观锁
5.为吞吐量要求苛刻的事务考虑乐观锁
6.显示声明打开事务
7.锁的行越少越好,锁的时间越短越好