【问题标题】:How to optimize serializing models data in Django Rest Framework?如何在 Django Rest Framework 中优化序列化模型数据?
【发布时间】:2018-05-11 12:55:30
【问题描述】:

我需要一个建议。有时我会遇到在 API 中需要从一些数据库表中检索大量数据的情况。当我尝试获取所有数据时 - 这需要很多时间。所以我用一辆小自行车在单个查询中检索数据。

例子:

class SomeModelSerializer(serializers.ModelSerializer):
    related_name1 = serializers.SerializerMethodField()
    related_name2 = serializers.SerializerMethodField()
    related_name3 = serializers.SerializerMethodField()

    def get_related_name1(self, obj):
        return self.values[obj.id].get('related_table1__name')

    def get_related_name2(self, obj):
        return self.values[obj.id].get('related_table1__related_table2__name')

    def get_related_name3(self, obj):
        return self.values[obj.id].get('related_table3__name')

    def __init__(self, *args, **kwargs):
        super(SomeModelSerializer, self).__init__(*args, **kwargs)
        # Collect all necessary data from BD.
        self.values = {}
        if self.instance:
            for some_model_values in self.instance.values(
                'id',
                'related_table1__name',
                'related_table1__related_table2__name',
                'related_table3__name'
            ).iterator():
                self.values[some_model_values['id']] = some_model_values

    class Meta:
        model = SomeModel
        fields = ('id', 'related_name1', 'related_name2', 'related_name3')

它适用于需要检索大量数据的情况,但它比简单的序列化程序需要更多的编码时间。 这是优化数据检索的好主意,还是您有针对这种情况的其他解决方案?

此解决方案针对任何查询集对 DB 进行 2 次查询。

【问题讨论】:

  • 这个问题属于codereview.stackexchange.com
  • 这个问题不适合 SO。在我看来,这是一个非常糟糕的主意。您正在牺牲代码的可维护性和可读性。保持代码简单。如果你真的需要更好的性能,我建议你使用 Redis 和 django-cacheops 来缓存你的查询。我对这些工具有非常积极的体验。
  • 虽然它不包含在您的问题中,但我认为您的问题可能是您的查询缺乏优化,很可能是 N+1 选择问题。当您要访问相关表中的每行数据时,请确保将.prefetch_related(...) 添加到 DRF 视图中的查询集。鉴于您所描述的,这听起来像是我以前见过的一个问题,使用 DRF 序列化程序渲染每一行需要至少一次额外的数据库往返。
  • 我同意 '.prefetch_related(...)' 和 '.select_related(...) '对于从相关实例中检索所有字段很有用。但我只收集在这个序列化程序中使用的字段。例如,如果 related_table2 有 15 个数据字段,而我只需要名称 - 我只收集一个字段。
  • 对这些字段使用注释将是高效且可读的 IMO

标签: python django django-models django-rest-framework


【解决方案1】:

我们可以简单地覆盖序列化器中的方法to_representation

class SomeModelSerializer(serializers.ModelSerializer):

  def to_representation(self, instance):
    data = super(SomeModelSerializer, self).to_representation(instance)
    data.update({
       'related_name1': instance.related_table1.name,
       'related_name2': instance.related_table2.name
       # more fields & values
    })
    return data

  class Meta:
    model = SomeModel
    fields = ('id',)

【讨论】:

  • 您的解决方案为每个相关表对数据库进行查询,同时我的解决方案为每个查询集对数据库进行 2 次查询。我尽量减少对 DB 的查询。
  • 我们可以在查询集上使用 prefetch_related 或 select_related。
  • 如果我正确理解 '.select_related(...) ' 的工作 - 它会从相关实例中检索所有字段。如果 related_table2 有 15 个数据字段 - 它会收集所有这些字段。在我的代码中 - 我只收集此序列化程序的必要字段。
【解决方案2】:
class SomeModelSerializer(serializers.ModelSerializer):
     value1 = serializer.CharField(source='related_table1.name', read_only=True)
     value2 = serializer.CharField(source='related_table1.related_table2.name')
     ............
     # and so on
     class Meta:
         model = ModelName
         fields = (
             'value1',
             'value2',
             ....
         )

我觉得,写的不多 相关信息:http://www.django-rest-framework.org/api-guide/fields/#source

【讨论】:

  • 您编写了标准的序列化程序解决方案,但它会进行大量查询来收集所有数据。我尝试优化收集。
【解决方案3】:

您是否尝试在使用SomeModelSerializerview/viewset 中使用prefetch_related?类似的东西

from rest_framework import viewsets

from myapp.models import SomeModel
from myapp.api.v1.serializers import SomeModelSerializer


class SomeModelViewSet(viewsets.ModelViewSet):
    serializer_class = SomeModelSerializer
    queryset = SomeModel.objects.prefetch_related(
        'related_table1',
        'related_table1__related_table2',
        'related_table3',
    )

【讨论】:

  • 是的,我使用 select_related 和 prefetch_related 测试了解决方案,它适用于小查询集,但对于具有数千个条目和 5-10 个相关表的查询集进行收集 - 它需要太多时间(并且收集了很多在这种情况下无用的数据)。
猜你喜欢
  • 2018-11-21
  • 2017-06-22
  • 2013-11-27
  • 1970-01-01
  • 1970-01-01
  • 2020-11-17
  • 1970-01-01
  • 2016-11-14
  • 2017-10-29
相关资源
最近更新 更多