关于最近遇到的坑 - if queryset

关于最近遇到的坑 - if queryset,第1张

概述背景 在python语法中,if obj是一种很简洁优雅的语法糖,可以用来判断字符串是否为空,某个参数是否为None,列表是否为空。所以,在面对queryset对象时便毫不犹豫的用if queryset来做判断,导致了性能问题。 class PageNumberPaginator(PageNumberPagination): .....省略无关代码 def paginate

背景

在Python语法中,if obj是一种很简洁优雅的语法糖,可以用来判断字符串是否为空,某个参数是否为None,列表是否为空。所以,在面对queryset对象时便毫不犹豫的用if queryset来做判断,导致了性能问题。

class PageNumberPaginator(PageNumberPagination):    .....省略无关代码    def paginate_queryset(self,queryset,request,ModelClass,vIEw=None):        self.total = None if queryset else get_total(queryset,ModelClass)        return PageNumberPagination.paginate_queryset(self,queryset=queryset,request=request)    def get_paginated_response(self,data,total=None):        query_count = self.page.paginator.count        return Response(OrderedDict([            (result,ok),(paging,OrderedDict([                (draw,self.draw),(page,self.page_param),(page_size,self.page_size),(count,query_count),(total,total if total else self.total or query_count),                ])),(data,data)            ]))

现在就来具体拆分并分析为什么不能用if queryset来判断queryset是否为空。

 

技术拆解-关于if判断

关于if判断的问题其实就是bool类型的判断。默认情况下

1.if会尝试bool(obj)

2.调用obj.__bool__()

3.调用obj.__x__()

具体解析,在判断布尔类型时,if会调用内置的bool(obj),此函数只能返回True或False。bool(obj)的背后会先尝试调用obj.__bool__()方法,存在则返回obj.__bool__()方法的结果,如果不存在,bool(obj)会尝试调用x.__len__()。若返回0,则bool返回0,否则True。

所以我们可以自己扩展并撰写一个自定义满足布尔类型判断的对象。

class LenFunc:    def __len__(self):        print(len step >>>>>>>)        return 1    def __repr__(self):        return LenFunc for __len__ testclass BoolFunc:    def __bool__(self):        print(bool step >>>>>>>)        return True    def __repr__(self):        return BoolFunc for __bool__ testclass MixFunc:    def __len__(self):        print(len step >>>>>>>)        return 1    def __bool__(self):        print(bool step >>>>>>>)        return True    def __repr__(self):        return MixFunc for __len__&__bool__ testfunc_List = [LenFunc(),BoolFunc(),MixFunc()]for obj in func_List:    print(obj)    if obj:        print(obj,is True \r\n)

运行结果如下:

特殊方法的调用是隐式的,一般情况下,我们是不需要关注这些内容的,除非对于我们自定义的类型进行扩展,想尝试python的内置语法糖,才需要满足协议。

另外如果是python内置的类型,比如List,str,bytearray那么cpython会抄近路,__len__实际上会直接返回PyVarObject里的ob_size属性。PyVarObject是表示内存中长度可变的内置对象的C语言结构体。直接读取这个值比调用一个方法快多了。

 

技术拆解-queryset

queryset实际上是Django的内置的queryset的实例。具体由来这边就不做阐述了,有兴趣的可以翻阅源码。下面贴上querySet的定义

 

相信对django熟悉的都知道queryset对数据库的查询结果是懒加载的。其实,真正的加载查询数据库数据的步骤,在__len__和__iter__都可以发现。

在__len__中会有两步调用

class querySet(object):    .....    def __len__(self):        self._fetch_all()        return len(self._result_cache)    def _fetch_all(self):        if self._result_cache is None:            self._result_cache = List(self._i@R_403_6704@ble_class(self))        if self._prefetch_related_lookups and not self._prefetch_done:            self._prefetch_related_objects()

第一步self._fetch_all()会查询可迭代对象内所有结果,即进行数据库查询 *** 作,赋值数据库查询的结果列表给当前实例queryset的_result_cache属性,把查询结果加载内存中。

第二步返回查询数据结果列表的长度

 

 

综上分析原因

当在分页查询内进行if queryset判断时,默认先寻找__bool__方法,当前对象没有,便会调用__len__方法。此时queryset作为一个懒加载对象,本身是在分页完聚焦在分页的那几条数据(10,20,50,100 whatever)进行数据库查询,却因为在if调用了__len__方法而提前被触发数据库查询。

当面临查询结果特别大的时候,如需要被分页的结果有4w条,数据便会被加载在内存中,导致内存占用过高,特别是在大并发的线上环境,尤其会影响性能,造成内存占用,内存紧张,影响服务整体性能,造成卡顿,甚至服务直接down掉

 

建议使用queryset.exists()来替代

def exists(self):    if self._result_cache is None:        return self.query.has_results(using=self.db)    return bool(self._result_cache)

django版本1.11.2

如果有兴趣,大家可以翻阅下源码,具体了解内容。

总结

以上是内存溢出为你收集整理的关于最近遇到的坑 - if queryset全部内容,希望文章能够帮你解决关于最近遇到的坑 - if queryset所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1191769.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-03
下一篇 2022-06-03

发表评论

登录后才能评论

评论列表(0条)

保存