注:1.6或者1.8以后的版本,请直接跳至标题4
一:背景
在处理http请求中,经常会遇到复杂的对数据库操作的业务逻辑。比如创建修改一系列的相关的对象。但是一旦其中某一处执行失败或出现异常,都要求回退到执行前的状态。这时候数据库的事务管理就非常重要了。
二:TransactionMiddleWare
可以通过settings.py中启用中间件来打开全局的事物管理。这个不推荐,一般不需开启,并且这个中间在1.8版本中删除。
所以settings.py文件中下面这个元组中可以将TransactionMiddleware代码行注释掉。到views.py视图函数里去添加事物处理。哪里需要就在那里添加,控制比较灵活。
MIDDLEWARE_CLASSES = (
'django.middleware.cache.UpdateCacheMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
#'django.middleware.transaction.TransactionMiddleware', # 事务管理中间件
'django.middleware.cache.FetchFromCacheMiddleware',
)
在中间件执行顺序中,位于TransactionMiddleware之后的中间件都将被纳入事务管理。
三:transaction.commit_on_success
在django1.6之前,transaction模块提供了以下几种方法供事务管理,注意以下方法将在1.8版本全部删除。
可用作装饰器的autocommit、commit_on_success、commit_manually
行代码级别的commit、rollback、savepoint、savepoint_rollback、savepoint_commit等
这里只讲一下最常用的commit_on_success
from django import transaction
1、装饰器用法
@transaction.commit_on_success()
def index(request):
pass
整个index的内容都会被纳入事务管理,出现异常则回滚,否则等待整个index方法执行成功后再提交至数据库。
2、代码块用法,推荐
def index(request):
work1()
try:
with transaction.commit_on_success():
work2()
except Exception:
pass
work3()
这里会将work2纳入事务管理,work1和work3没有纳入。在例子中with外层使用了try抓取异常,这能保证事务管理的同时又能使view正常执行,返回正常的response[200]而不是直接抛出异常500。
需要注意的是,假如你在work2中使用了try抓取异常,那么抓取的这部分将不会进行事务管理。示例代码如下
def index(request):
work1()
try:
with transaction.commit_on_success():
a.save() # in transaction
try:
b.save() # NOT in transaction
except:
pass
c.save() # in transaction
except:
pass
四:transaction.atomic
从django1.6开始,django提供了atomic来代替之前几种方法。
依据Django1.6的文档,“Django提供了一种简单的API去控制数据库的事务交易...原子操作用来定义数据库事务的属性。原子操作允许我们在数据库保证的前提下,创建一堆代码。如果这些代码被成功的执行,所对应的改变也会提交到数据库中。如果有异常发生,那么操作就会回滚。”
用法和上例一致,可以采用装饰器用法或代码块用法,推荐代码块。
def index(request):
try:
with transaction.atomic():
work2()
except Exception:
pass
五:多数据库的事务管理坑
注意!注意!注意!当给django配置了多个数据库时,所有事务管理的方式都只会影响settings.py DATABASES中默认的数据库(default)。如:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
......
},
'zoo': {
'ENGINE': 'django.db.backends.mysql',
......
}
}
此时如果版本控制中有对zoo数据库的操作,django默认会跑去default数据库中进行回滚,结果当然是静默失败。
对此,只能显式声明要操作的数据库,哪怕django完全可以通过代码追溯到操作的数据库是哪个,目前的django 1.9版本仍然如此。
def index(request):
try:
with transaction.atomic(using='zoo'): # transaction.commit_on_success(using='zoo')
work2()
except Exception:
pass
希望以后的版本会改进此处。
六:关于性能
拿MySQL/InnoDB举例
MySQL的事务支持不是绑定在MySQL服务器本身,而是与存储引擎相关。
MyISAM:不支持事务,用于只读程序提高性能
InnoDB:支持ACID事务、行级锁、并发
Berkeley DB:支持事务
事务的ACID属性只能通过限制数据库的同步更改来实现,从而通过对修改数据加锁来实现。直到事务触发COMMIT或ROLLBACK语句时锁才释放。 缺点是后面的事务必须等前面的事务完成才能开始执行,吞吐量随着等待锁释放的时间增长而递减。
MySQL/InnoDB通过行级锁来最小化锁竞争。这样修改同一table里其他行的数据没有限制,而且读数据可以始终没有等待。
MySQL/InnoDB默认给每条SQL语句都声明为一个事务,即使大部分数据库操作中都用不到事务管理。
事务设计原则
1,保持事务短小
2,尽量避免事务中rollback
3,尽量避免savepoint
4,默认情况下,依赖于悲观锁
5,为吞吐量要求苛刻的事务考虑乐观锁
6,显示声明打开事务
7,锁的行越少越好,锁的时间越短越好
django同样遵循此原则,从1.8亦或1.9开始,django默认将每条SQL语句都取消事务声明,用来减少每次http请求所造成的数据库操作未使用的事务管理消耗,提升性能,只有手动使用transaction.atomic才会保留MySQL的事务管理。
网友评论