Django transaction 误用之后遇到的一个问题与解决方法
今天在调试项目开发好的一个模块的时候,发现了一个很诡异的现象,最后追踪发现是因为在项目中事务处理有误所致。这个问题坑了我好一会,所以记录一下,以免再踩坑。下面开始详述。
我们都知道 Django 框架提供了很多的开启事务的方式,这在后面会有详述。笔者比较喜欢使用的是使用 @transaction.atomic 装饰的方式来启动一个事务。因为通过该形式,我们可以在保证了 db 原子操作的同时,还可以自定义事务涉及的模块范围。atomic 还可以通过上下文的形式来使用,比如:
with transaction.atomic():
transaction_plalala()
.
.
.
好了。既然如此,那就用起来吧。一阵啪啪啪之后,开发完了,调试的 log 也打了,开测吧。诡异的事情在此发生了。在log中明明打印出来了数据库中生成的主键id,但是在数据库中死活查不出来。WTF?!通过 SHOW CREATE TABLE xxx 也看到了 xxx 表的自增值已经发现了变化。但咋就没有了呢?谁动了我的数据?这时候,不得不从 view 开始看起,一直追踪到了开发的新的模块。view到调用模块没有问题。这是什么情况?有鬼?肯定不是。莫非在某个地方,配置了一个新的事务,而这个事务是包含了整个 view?因为笔者只发现了 view 中有个 raise exception的操作。猜测只能是这样了。因为我新开发的模块没有问题的,这我是在其他的 view 中进行过验证的。
于是乎,笔者看了下 Django 中的事务开启的方式,发现了果然有一个将事务绑定到 HTTP 请求上的开启的方式。它的开启方式是在 db 中指定 ATOMIC_REQUESTS=True 开启。打开 settings 文件,找到了对应的配置,果然,问题就是出在这里!咋办?既然这边人家已经配置了,在不影响到其他 view(说不定已经依赖了此事务操作)的情况下,怎么去关闭这个配置呢?答案是通过 transaction.non_atomic_requests 装饰view。好了。测试一下,确实可以了。问题解决了。下面的是官方 Demo:
from django.db import transaction
@transaction.non_atomic_requests
def my_view(request):
do_stuff()
@transaction.non_atomic_requests(using='other')
def my_other_view(request):
do_stuff_on_the_other_database()
在看 Django 中的官方文档后,发现,其不推荐这么做。原因是如果将事务跟 HTTP 请求绑定到一起的话,view 是依赖于应用程序对数据库的查询语句效率和数据库当前的锁竞争情况。当流量上来的时候,性能会有影响。那么,该怎么去保证既可以使用事务呢?
第一种就是上面所说的这种,在 database 中通过指定 ATOMIC_REQUESTS 的形式来将事务绑定到HTTP请求上。接触 view 在事务中的操作的方式是在 view 上装饰 transaction.non_atomic_requests,前面已经说过,具体也可以阅读 Django 官方文档。
default_db = {
"ENGINE": "",
"NAME": "",
"USER": "",
"PASSWORD": "",
"HOST": "",
"PORT": "",
"OPTIONS": "",
"ATOMIC_REQUESTS": True,
}
还有一种方式是笔者喜欢用的那种,通过 transaction.atomic 来更加明确的控制事务。atomic允许我们在执行代码块时,在数据库层面提供原子性保证。 如果代码块成功完成, 相应的变化会被提交到数据库进行commit;如果执行期间遇到异常,则会将该段代码所涉及的所有更改回滚。
这里,我们在使用此方式的时候还需要注意一点,就是:避免在 atomic里捕获异常!当一个原子块执行完退出时,Django会审查是正常提交还是回滚。如果你在原子块中捕获了异常的句柄, 你可能就向 Django 隐藏了问题的发生。这可能会导致意想不到的后果。正确捕捉数据库异常应该是类似上文所讲 ,基于atomic 代码块来做。若有必要,可以额外增加一层atomic代码来用于此目的。这种模式还有另一个优势:它明确了当一个异常发生时,哪些操作将回滚。在底层,Django的事务管理代码:
- 当进入到最外层的 atomic 代码块时会打开一个事务;
- 当进入到内层atomic代码块时会创建一个保存点;
- 当退出内部块时会释放或回滚保存点;
- 当退出外部块时提交或回退事物。
你可以通过设置savepoint 参数为 False来使对内层的保存点失效。如果异常发生,若设置了savepoint,Django会在退出第一层代码块时执行回滚,否则会在最外层的代码块上执行回滚。 原子性始终会在外层事物上得到保证。这个选项仅仅用在设置保存点开销很明显时的情况下。它的缺点是打破了上述错误处理的原则。
from django.db import transaction
def viewfunc(request):
# This code executes in autocommit mode (Django's default).
do_stuff()
with transaction.atomic():
# This code executes inside a transaction.
do_more_stuff()
Django 支持 autocommit 来为每个SQL语句在执行时都会启动一个事务。如果想要关闭,可以通过在配置文件中设置 AUTOCOMMIT=False 参数来关闭。这样,Django 将不能启用 autocommit,也不能执行任何 commits。这就需要你对每个事物执行明确的commit操作。因此,这最好只用于你自定义的事物控制中间件或者是一些比较奇特的场景。
参考:
Django transaction 误用之后遇到的一个问题与解决方法的更多相关文章
- 一个简单的解决方法:word文档打不开,错误提示mso.dll模块错误。
最近电脑Word无故出现故障,无法打开,提示错误信息如下: 问题事件名称: APPCRASH应用程序名: WINWORD.EXE应用程序版本: 11.0.8328.0应用程序时间戳: 4c717ed1 ...
- C# 该行已经属于另一个表 的解决方法[转]
该文转自:http://blog.sina.com.cn/s/blog_48e4c3fe0100nzs6.html DataTable dt = new DataTable(); dt = ds.Ta ...
- Emgu CV的一个异常的解决方法
今年组里有大项目落我头上了,并不能像去年一样回家还能搞搞Cocos2dX,一把老泪流了下来... 回到正题,由于组里需要做一个显示板的自动测试项目,涉及到Computer Vision.不得不说,这才 ...
- python 迭代器 一个奇怪的解决方法
一般我们在类里面写迭代器都是如下写法: class IterableSomthing: def __iter__(self): return self def __next__(self): retu ...
- django框架使用mysql报错,及两种解决方法
1.django框架 settings.py文件中部分代码: DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3' ...
- The “SignFile” task was not given a value for the required parameter “CertificateThumbprint”的一个简单的解决方法
这个只是其中一种解决方法,而且不是万能的 1. 由提示内容可以看出,这个一个 sign(认证)的问题, 在出现这个问题的项目上,鼠标右键,选择properties,然后选择signing. 2. 选择 ...
- confirm显示数组中的内容时,总是带一个逗号分隔的解决方法
问题的关键 就是在给confirm显示之前,将数组转换成字符串,并以每个数组的元素为一个字符串,加上一个换行回车符即可: 代码中的背景色 为关键的点 <script type="tex ...
- export的变量另开一个终端失效解决方法
有时候,我们需要把一个export的变量全局话,否则每开一个终端又需要重新export,很是麻烦 首先直接export某个变量的话就只能在当前子终端生效,另开一个终端就失效了 如果修改.bash_pr ...
- C#两个DataTable拷贝问题:该行已经属于另一个表的解决方法
一.DataTable.Rows.Add(DataRow.ItemArray); 二.DataTable.ImportRow(DataRow) 三.设置DataTable的tablename,然后.R ...
随机推荐
- git checkout -b 报错
有时候在git中checkout -b 出现如下报错 $ git checkout -b test --track origin/master fatal: Cannot update paths a ...
- cocos2d_android 第一个游戏
依据上一篇文章.创建好cocos2d--android的开发环境 先上效果图 实现该效果的代码: package com.cn.firstgame; import org.cocos2d.layers ...
- hive安装用mysql作为元数据库,mysql的设置
mysql的设置 在要作为元数据库的mysql服务器上建立hive数据库: #建立数据库 create database if not exists hive; #设置远程登录的权限 GRANT AL ...
- 区间dp学习笔记
怎么办,膜你赛要挂惨了,下午我还在学区间\(dp\)! 不管怎么样,计划不能打乱\(4\)不\(4\).. 区间dp 模板 为啥我一开始就先弄模板呢?因为这东西看模板就能看懂... for(int i ...
- nginx 代理https后,应用redirect https变成http --转
原文地址:http://blog.sina.com.cn/s/blog_56d8ea900101hlhv.html 情况说明nginx配置https,tomcat正常http接受nginx转发.ngi ...
- 一些 <link> 标记分享
<link rel="alternate" media="handheld" href="#" /> <link rel= ...
- SQL--通过身份证号得到年龄的
/* =======================================创 建 人:CuiYaChao创建日期:2017-08-16功能描述:通过身份证号来计算年龄单元名称: Fun_Ge ...
- HDU 2515 Yanghee 的算术【找规律】
题意:中文的题目 找规律可以发现 sum[1]=a[1]+a[2] sum[2]=a[1]+a[3] sum[n]=a[2]+a[3] 解出a[1],就可以求出其他的了 #include<ios ...
- Git强制覆盖master分支
在开发中,通常会保持两个分支master分支和develop分支,但是如果因为develop上面迭代太多而没有及时维护master,最后想丢弃master而直接将测试确认过的develop强推到mas ...
- HDU-6109 数据分割 并查集(维护根节点)
题目链接:https://cn.vjudge.net/problem/HDU-6109 题意 给出多组等式不等式 对于每一个式子,首先判断是否不可能 如果不可能,记录本组正确式子的个数,然后进入下一组 ...