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 ...
随机推荐
- Sql_Server中怎样推断表中某列是否存在
/*推断表AA中是否存在AA_ID这一列.假设不存在,则新增*/ IF NOT EXISTS (SELECT 1 FROM syscolumns INNER JOIN sysobjects ON sy ...
- 工作笔记5.JAVA图片验证码
本文主要内容为:利用JAVA图片制作验证码. 设计思路: 1.拷贝AuthImageServlet.class图片验证码 2.配置web.xml 3.JSP中,调用封装好的AuthImageServl ...
- sass08 if while for each
scss @function getzIndex($layer: default){ $zindexMap: (default: 1, modal: 1000, dropdown: 500, grid ...
- 使用ShareSDK实现第三方授权登录、分享以及获取用户资料效果,项目中包含:源码+效果图+项目结构图
[Android应用开发详解]第01期:第三方授权认证(一)实现第三方授权登录.分享以及获取用户资料 由于公司项目的需要,要实现在项目中使用第三方授权登录以及分享文字和图片等这样的效果,几经波折, ...
- bzoj1055: [HAOI2008]玩具取名(dp)
1055: [HAOI2008]玩具取名 题目:传送门 简要题意: 就是固定四个字母,给出这四个字母分别可以由哪两个字母组成,然后在给你一个字符串,要求把这个字符串还原成原始的四个字母的其中一个. 题 ...
- Webfont 的兼容性问题[持续更新]
低版安卓手机的 webview 显示不了,另外黑莓手机显示出来是这样: 生成工具: 离线字体生成工具:webfont 在线字体生成平台:icomoon.io, iconfont.cn均有问题 其他一些 ...
- 重复执行shell脚本。
while ((1)); do gclient runhooks; sleep 2; donewhile ((1)); do ifconfig; sleep 1; done
- 使用U盘作为启动盘安装ubuntu系统
一.使用U盘刻录镜像 1.安装之后我们打开软件,点击文件打开,找到我们刚才进行下载的Ubuntu的ISO文件,然后点击打开,完成ISO文件的加载.接着我们插入U盘,点击UltraISO启动选项,然后 ...
- yes---重复输出指定的字符串
yes命令在命令行中输出指定的字符串,直到yes进程被杀死.不带任何参数输入yes命令默认的字符串就是y. 语法 yes(参数) 参数 字符串:指定要重复打印的字符串. 实例 [root@localh ...
- 05002_Linux的安装
1.虚拟机VMware workstation的安装 (1)下载链接:VMware workstation下载 密码:52wt: (2)双击VMware-workstation-full-10.0.2 ...