缓存使用django orm构建的sql查询
django-prepared-queries的Python项目详细描述
Django准备的查询
django_pq
允许缓存使用django orm生成的sql并重用缓存的sql
只替换新参数值的查询。
简短示例
一些开发人员认为django orm很慢。如果你的代码看起来 像这样:
fromdjango.dbimportmodelsfromcountries_field.fieldsimportcountries_isnull,countries_containsdeffilter_queryset(self,domains=None,**kwargs):query=models.Q()ifdomains:query&=((models.Q(allow_domains__name__in=domains)|models.Q(allow_domains__isnull=True))&(~models.Q(deny_domains__name__in=domains)|models.Q(deny_domains__isnull=True)))else:query&=(models.Q(allow_domains__isnull=True)&models.Q(deny_domains__isnull=True))user_agent=kwargs.pop('user_agent',None)ifuser_agent:query&=(models.Q(user_agents=user_agent)|models.Q(user_agents__isnull=True))else:query&=models.Q(user_agents__isnull=True)country=kwargs.pop('country')ifcountry:query&=countries_isnull()|countries_contains([country])else:query&=countries_isnull()returnself.get_queryset().filter(query)
生成的sql查询非常长,在我们的例子中,它占到http的50% 请求处理。如果我们可以缓存生成的sql并替换 实际参数值而不是重复重查询集筛选?
好吧,用django_pq
你可以做以下的事情。
fromdjango.dbimportmodelsimportdjango_pq# Add caching decorator for heavy queryset constructing method@django_pq.substitute_lazy()deffilter_queryset_lazy(self,domains=None,**kwargs):query=models.Q()# branches in decorated function must check real value instead of Lazy # wrapper, because actual value this time could be False.ifdjango_pq.reveal(domains):# You pass Lazy wrappers in to any lookup parameters for queryset,# and these Lazy wrappers remain lazy until it's time to query the # database.query&=((models.Q(allow_domains__name__in=domains)|models.Q(allow_domains__isnull=True))&(~models.Q(deny_domains__name__in=domains)|models.Q(deny_domains__isnull=True)))else:query&=(models.Q(allow_domains__isnull=True)&models.Q(deny_domains__isnull=True))# ... # # modify other parts of queryset constuction with respect of lazy nature of# arguments.returnself.get_queryset().filter(query)deffilter_queryset(self,**kwargs):# wrap parameters into context manager so Lazy wrappers could get actual# values when they need.withdjango_pq.LazyContext(**kwargs):queryset=self.filter_queryset_lazy(**kwargs)# queryset is now RawQuerySet with Lazy wrappers in params.# database queries should be performed within LazyContext.returnqueryset.first()
就这样-您的queryset生成代码被缓存。
准备缓存代码的规则
- 不要检查lazy wrappers的任何内容-使用
reveal()
检查实际的 参数值。即Lazy(None) is not None
总是正确的(这不是 你真正的意思)。 - 不要将模型实例作为参数传递。这允许模型实例方法 调用并可能导致无法从实际中检测到的隐式分支 参数列表。而是传递主键值。
- 不要在缓存的方法中查询数据库-无法检测到分支。
- 将所有
if
表达式作为新参数添加到方法中 使用适当的缓存。 - 不要将空列表作为参数值传递。django orm检查
空并从wherenode删除空查找(关于布尔值
代数规则)。改为传递
None
。 - 不要在queryset过滤中使用任何易失性值,如
datetime.now()
; 将其作为参数传递。 - 在添加缓存之前,使用100%分支覆盖率测试代码。
规范化参数
帮助您规范化传入缓存函数的参数LazyContext
当
正在输入上下文。
fromdjango.dbimportmodelsfromdjango_pqimportLazyContextdefmodel_to_pk(kwargs):fork,vinlist(kwargs.items()):ifisinstance(v,models.Model):kwargs[k]=v.pk# Model -> Model.pkreturnkwargsdefempty_list_to_none(kwargs):fork,vinlist(kwargs.items()):ifisinstance(v,list)andnotv:kwargs[k]=None# [] -> Nonereturnkwargsdeffilter_queryset(self,**kwargs):withLazyContext(model_to_pk,empty_list_to_none,**kwargs)aslazy_kwargs:queryset=self.filter_queryset_lazy(**lazy_kwargs)returnqueryset.first()
工作原理
- 首先,
substitute_lazy()
decorator用惰性包装器包装所有参数, 在sql生成完成之前,with wrapper保持“惰性”。 - 您的代码被调用了两次,使用惰性包装作为参数,并使用实际的 值,以确保惰性结果与本机QuerySet相同。
- 如果sql和规范化参数匹配,则缓存一个
RawQuerySet
实例 以惰性包装器作为参数。 - 缓存键尊重任何参数和某些常量的存在,如
True, False, 0, 1, None
。 - 在“缓存命中”情况下,新的实际参数值将从
懒散的上下文进入
RawQuerySet.params
这是缓存的结果。 - 如果你做得对,
RawQuerySet
会表现得很正常QuerySet
,或者(更正确地)作为模型实例迭代器。