jasper的技术小窝

关注DevOps、运维监控、Python、Golang、开源、大数据、web开发、互联网

从一次celery踩坑中谈谈Queryset的懒加载

作者:jasper | 分类:django | 标签:       | 阅读 1993 次 | 发布:2014-12-07 11:56 p.m.

最近由于项目需要在后台实现高并发,所以用了celery这个成熟的开源框架,在用其对django的数据库做一些操作时,不料却踩到Queryset的坑了(其实也不能叫做坑,别人本来就是这样的机制),在这里我来详细地说一说。 当时的情况是这样的,用celery同时下发了两个job,这两个job都是异步的,其中这两个job的过程是对数据库里的同条记录的不同字段做修改,使用的当然是django的ORM的处理方法。 这两个job大体是这样的:

job1:

obj=Mytable.objects.get(id=1)
obj.field1 = xxx
obj.save()

job2:

obj=Mytable.objects.get(id=1)
obj.field2 = yyy
obj.save()

就是这样很简单的逻辑,但是在结果中却发现,无论执行多少次,都只是其中的一个成功了。我以为肯定是celery出什么问题了,加入log信息后再次执行,结果发现的确两个job都是执行了的。而且多次试验后,发现两个job是否执行还是随机的。这是什么个情况,难道是撞鬼了不成! 调试了许久都不知所然,只有去向大神讨教。大神听闻说道,应该不是celery的问题,而是django操作数据库的问题,因为queryset是懒加载的,可能会造成这样的问题,叫我可以试一试用sql语句,看还可不可以解决。

好吧,待我改为sql语句的方式时,发现问题引刃而解,两个job都成功地执行了。 追根溯源一下,所谓的“懒加载”,就是指Queryset在获取的时候不会操作数据库,只有有在需要的时候才会被执行。好像说的有点不清楚,举个栗子吧:

obj=Mytable.objects.get(id=1)  #不会操作数据库
print obj.field   #这里才会去真正操作数据库

而且Queryset是保存在内存的,有缓存机制,例如:

obj=Mytable.objects.get(id=1)
print obj.field     # 对象会被从数据库查询出来
print obj.field    # 第二次访问的缓存对象,不会再次执行查询

那么在我所遇到的问题中,就是只有在save的时候才会去操作数据库。在job1中获得obj对象后,由于是同时执行,job2就从缓存中得到了这个Queryset,这时在job1和job2同时执行save的时候,会产生一个对数据库的操作,而这个操作是针对同一个Queryset的,所以只会有一条sql语句发到数据库。但又因为是两个独立的线程,相互之间不会通信,所以下发的操作中只会有一个字段被修改,而这个字段是根据两个job中save语句的执行顺序来定的。

那但我换为sql语句时,就相当于两个完全没关系的sql发到了数据库端,在数据库端会有一个小小的队列把这两个语句放起来,然后依次执行。 当然,这种情况也只是并发的时候才会遇到。

写到这儿,我有想到之前我有过一个多表查询的case,而且在查询时会有好几个for循环。采用django的ORM时,要花费大约两秒钟,这是不能忍受的。但是用一个sql语句就可以解决,花费时间也是毫秒级的。这是因为在for循环中每次都会有一个数据库连接,再执行一个sql,这样耗时当然会很长了。

看来有时候真不能完全迷信django带给我们的这种方便的ORM的机制啊,当然我们可以在运用的时候,尽可能地少连接数据,这里有一篇文档,介绍了一些优化方法,大家不妨读一读,会很有收获的https://docs.djangoproject.com/en/1.7/topics/db/optimization/

睡觉啦,呼呼。。。。。。


转载请注明出处:http://www.opscoder.info/queryset_lazy.html

其他分类: