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 ...
随机推荐
- MongoDB查询、索引和聚合
初始化mongodb数据库 > use deng switched to db deng > db.createCollection("jingdong") #无參数 ...
- Java 深拷贝和浅拷贝 利用序列化实现深拷贝
Java 深拷贝和浅拷贝 转自:http://www.cnblogs.com/mengdd/archive/2013/02/20/2917971.html 深拷贝(deep clone)与浅拷贝(sh ...
- redis作为缓存场景使用,内存耗尽时,突然出现大量的逐出,在这个逐出的过程中阻塞正常的读写请求,导致 redis 短时间不可用
redis 突然大量逐出导致读写请求block 内容目录: 现象 背景 原因 解决方案 ref 现象 redis作为缓存场景使用,内存耗尽时,突然出现大量的逐出,在这个逐出的过程中阻塞正常的读写请 ...
- 添加本地 yum源
添加本地 yum源 yum-updatesd.conf yum的主配置文件 [root@zhou ~]# cd /etc/yum.repos.d/ [root@zhou yum.repos.d]# ...
- 有用的 Bash 快捷键清单
作者: Sk 译者: LCTT Sun Yongfei 现如今,我在终端上花的时间更多,尝试在命令行完成比在图形界面更多的工作.随着时间推移,我学了许多 BASH 的技巧.这是一份每个 Linux 用 ...
- 利用中间件 mysql_proxy 完成 mysql 的负载均衡和读写分离
安装 mysql_proxy cd /usr/local/src wget http://mysql.cdpa.nsysu.edu.tw.Downloads/MySQL - ...
- Centos安装masscan
1.yum install git gcc make libpcap-devel2.git clone https://github.com/robertdavidgraham/masscan3.cd ...
- asp.net.core网站重启后登陆无效问题(部署在IIS)
一.问题 在使用asp.net.core时,把网站发布到IIS后,在后续更新中需要停止网站,然后重启网站,发现已经登陆的用户会退出登陆.过程如下 1.登陆代码(测试) [AllowAnonymous] ...
- 题解 P3374 【【模板】树状数组 1】
恩,这是AC的第一道树状数组呢. 本蒟蒻以前遇到RMQ问题一般都用线段树或ST表,可惜ST表不支持在线修改,而线段树代码量又太大. 如今终于找到了折中方案:树状数组!!!!代码量小,还支持修改! 树状 ...
- BestCoder Round #11 (Div. 2)
太菜,仅仅能去Div2.(都做不完 ORZ... 各自是 HDU: 5054pid=5054"> Alice and Bob 5055Bob and math problem 5056 ...