【问题标题】:Optimize Django Rest ORM queries优化 Django Rest ORM 查询
【发布时间】:2019-08-15 01:10:19
【问题描述】:

我是一个 react 前端,django 后端(用作 REST 后端)。 我继承了该应用程序,它使用许多模型和序列化加载所有用户数据。它加载非常缓慢。 它使用过滤器查询单个成员,然后将其传递给序列化器:

found_account = Accounts.objects.get(id='customer_id')
AccountDetailsSerializer(member, context={'request': request}).data

那么嵌套的序列化器就这么多:

AccountDetailsSerializers(serializers.ModelSerializer):
   Invoices = InvoiceSerializer(many=True)
   Orders = OrderSerializer(many=True)
   ....

从查看日志看来,ORM 发出了如此多的查询,这太疯狂了,对于某些端点,我们最终会得到大约 50 到 60 个查询。

  1. 我应该尝试研究使用 select_related 和 prefetch 还是跳过所有这些并尝试编写一个 sql 查询来执行多个连接并以 json 格式一次获取所有数据?

  2. 当我传入单个对象(get 的结果)而不是序列化程序的查询集时,如何定义预取/select_related?

  3. 一些 db 实体之间没有链接,这意味着不是 fk 或多对多关系,只是持有一个与另一个有 id 的字段,但在数据库中没有强制执行这种关系?这对我来说会是个问题吗?这是否意味着我应该跳过 select_related 方法并编写一个客户 sql 用于获取?

  4. 您建议如何对这个噩梦般的查询进行性能调优?

【问题讨论】:

  • 在您引用的特定情况下,您是一次只处理一个Account(如代码所示)还是几个(重复get() 查询)?
  • @EndreBoth 一次一个帐户,有许多相关的嵌套模式
  • 那么你必须有一个非常复杂的层次结构才能陷入困境 - 检查你是否真的需要预先嵌套所有的东西,或者你是否可以按需加载它们。
  • 是的,我正在经历这个过程,但与此同时,我更愿意在我可以的地方进行连接...如何为 get 查询提供预取/选择相关的?因为它返回一个模型,而不是查询集?
  • 如果您将get 替换为filter(...)[0],您最终会得到相同的对象,但您可以实现任何预取或预选。但这与 Jens 提出的方法没有太大区别。

标签: django performance django-models django-rest-framework query-performance


【解决方案1】:

我建议您先看看prefetch_related 的效果。它可能对加载时间产生重大影响,并且实施起来相当简单。按照你上面的例子,这样的事情可以单独显着减少加载时间:

AccountDetailsSerializers(serializers.ModelSerializer):

    class Meta:
        model = AccountDetails
        fields = (
            'invoices',
            'orders',
        )

    invoices = serializers.SerializerMethodField()
    orders = serializers.SerializerMethodField()

    def get_invoices(self, obj):
        qs = obj.invoices.all()\
            .prefetch_related('invoice_sub_object_1')\
            .prefetch_related('invoice_sub_object_2')
        return InvoiceSerializer(qs, many=True, read_only=True).data

    def get_orders(self, obj):
        qs = obj.orders.all()\
            .prefetch_related('orders_sub_object_1')\
            .prefetch_related('orders_sub_object_2')
        return OrderSerializer(qs, many=True, read_only=True).data 

至于您的架构问题,我认为还有很多其他因素会影响您是否应该重构代码库以及在多大程度上重构代码库。但总的来说,如果您与 Django 和 DRF 结合,如果您能够接受这些框架的习惯用法和模式,而不是试图通过自己的修复来购买它们,那么您将获得更好的开发人员体验。

【讨论】:

  • 但是当我不返回查询集但我返回一个get..意思是我做Account.objects.get(filter)时,如何使用prefetch_related和select_related。如果可能的话,我希望该帐户已经在一个查询中获得所有相关信息..所以当我将一个对象传递给第一个 serialieer 时?意思是 Accounts.objects.filter(),我如何确保所有的序列化方法都使用预取?我应该在获取之前添加预取吗?或者你是怎么处理的?
【解决方案2】:

如果不详细查看代码(和分析结果),就没有灵丹妙药。

唯一不费吹灰之力的就是在模型和数据库中强制执行关系。这可以防止一大堆错误,鼓励使用标准化的高性能访问(而不是当场编造 SQL,这往往可能是错误的缓慢)并使您的代码更短并且更具可读性。

除此之外,50-60 个查询可能很多(如果您可以用一两个完成相同的工作)或者它可能恰到好处 - 这取决于您使用它们实现的目标。

prefetch_relatedselect_related 的使用很重要,是的,但前提是正确使用;否则它会减慢你的速度而不是加快你的速度。

如果您需要数据,嵌套序列化程序是正确的方法 - 但如果您希望它们更快,则需要在视图集中正确设置查询集。

对慢视图的主要部分进行计时,检查发送的 SQL 查询并检查您是否真的需要返回的所有数据。

然后,您可以查看痛处并在重要的地方赢得时间。用完整的代码示例询问关于 SO 的具体问题也可以让你更快。


如果您只有一个顶级对象,您可以改进@jensmtg 提供的方法,在该级别执行您需要的所有预取,然后只使用ModelSerializers(不是SerializerMethodField s) 访问预取的对象。查看允许嵌套预取的Prefetch 对象。

但请注意prefetch_related 不是免费的,它涉及到相当多的Python 处理;您最好使用带有values()values_list 的平面(类似db-view)连接查询。

【讨论】:

  • 但是当我不返回查询集时如何使用 prefetch_related 和 select_related,但我返回一个 get..意思是我做 Account.objects.get(filter),而不是 Accounts.objects。 filter(),我如何确保所有的序列化方法都使用预取?我应该在获取之前添加预取吗?或者你是怎么处理的?
猜你喜欢
  • 2021-06-16
  • 2022-01-08
  • 2023-03-30
  • 2018-10-23
  • 1970-01-01
  • 2021-03-02
  • 2010-12-11
  • 2018-04-03
  • 2011-10-08
相关资源
最近更新 更多