使用 Django Rest 框架的 Mysql 多连接性能

Posted

技术标签:

【中文标题】使用 Django Rest 框架的 Mysql 多连接性能【英文标题】:Mysql Multiple Join Perfromance with Django Rest Framework 【发布时间】:2019-05-24 20:51:53 【问题描述】:

我一直在努力解决一个问题,我相信每个人在某些时候都会遇到。我现在有一个包含 150k 产品的小型数据库。 (在我写这篇文章时增加。)

我正在将 DRF 用于 api,并且一直在努力解决我拥有很多产品的类别性能。

I.E 我有一个名为“连衣裙”的类别,其中包含 34633 产品。 我的数据库的设计方式是我在它下面有几个关系。

产品有类别、属性、颜色、尺寸、相关产品M2M

查询

计数查询809.83ms

SELECT COUNT(*) 
FROM (
  SELECT DISTINCT `catalog_products`.`id` AS Col1 
  FROM `catalog_products` 
  INNER JOIN `catalog_products_category` ON (`catalog_products`.`id` =
                           `catalog_products_category`.`products_id`) 
  WHERE (`catalog_products`.`deleted` = 0 
     AND `catalog_products`.`in_stock` = 1 
     AND `catalog_products_category`.`categories_id` = 183)
) subquery

结果查询2139.52ms

SELECT DISTINCT `catalog_products`.`id`, `catalog_products`.`sku`,
  `catalog_products`.`title`, `catalog_products`.`old_price`,
  `catalog_products`.`price`, `catalog_products`.`sale`,
  `catalog_products`.`original_categories`,
  `catalog_products`.`original_conv_color`, `catalog_products`.`original_sizes` 
FROM `catalog_products` 
INNER JOIN `catalog_products_category` ON (`catalog_products`.`id` =
                         `catalog_products_category`.`products_id`) 
WHERE (`catalog_products`.`deleted` = 0 
  AND `catalog_products`.`in_stock` = 1 
  AND `catalog_products_category`.`categories_id` = 183) 
ORDER BY `catalog_products`.`title` ASC LIMIT 48

正如您所见,查询的时间非常长,但这是我应用过滤器时棘手的部分,即我选择颜色过滤器并且尺寸时间开始减少。

应用过滤器的查询

计数查询264.63ms

SELECT COUNT(*) FROM (
  SELECT DISTINCT `catalog_products`.`id` AS Col1 
  FROM `catalog_products` 
  INNER JOIN `catalog_products_color` ON (`catalog_products`.`id` =
                           `catalog_products_color`.`products_id`) 
  INNER JOIN `catalog_products_category` ON (`catalog_products`.`id` =
                           `catalog_products_category`.`products_id`) 
  INNER JOIN `catalog_sizethrough` ON (`catalog_products`.`id` =
                            `catalog_sizethrough`.`product_id`) 
  WHERE (`catalog_products`.`deleted` = 0 
    AND `catalog_products`.`in_stock` = 1 
    AND `catalog_products_color`.`color_id` = 1 
    AND `catalog_products_category`.`categories_id` = 183 
    AND `catalog_sizethrough`.`size_id` IN (262) 
    AND `catalog_sizethrough`.`stock` = 1)
) subquery

结果查询351.43ms

SELECT DISTINCT `catalog_products`.`id`, `catalog_products`.`sku`,
  `catalog_products`.`title`, `catalog_products`.`old_price`,
  `catalog_products`.`price`, `catalog_products`.`sale`,
  `catalog_products`.`original_categories`,
  `catalog_products`.`original_conv_color`,
  `catalog_products`.`original_sizes` 
FROM `catalog_products` 
INNER JOIN `catalog_products_color` ON (`catalog_products`.`id` =
                         `catalog_products_color`.`products_id`) 
INNER JOIN `catalog_products_category` ON (`catalog_products`.`id` =
                         `catalog_products_category`.`products_id`) 
INNER JOIN `catalog_sizethrough` ON (`catalog_products`.`id` =
                          `catalog_sizethrough`.`product_id`) 
WHERE (`catalog_products`.`deleted` = 0 
  AND `catalog_products`.`in_stock` = 1 
  AND `catalog_products_color`.`color_id` = 1 
  AND `catalog_products_category`.`categories_id` = 183 
  AND `catalog_sizethrough`.`size_id` IN (262) 
  AND `catalog_sizethrough`.`stock` = 1) 
ORDER BY `catalog_products`.`title` ASC 
LIMIT 48

我已经尝试了很多方法来解决此问题,但无法解决此问题,我需要提高页面加载速度,但由于查询花费的时间更长,因此对用户来说并不是那么好。 我使用了 Eager 加载,因此除非您添加任何内容,否则它对改进没有帮助。

代码

序列化器

class ProductsListSerializer(serializers.ModelSerializer):

images = ImagesSerializer(many=True, source='get_first_two_images')
related_color = serializers.SerializerMethodField()

def get_related_color(self, obj):
    return obj.related_color.count()

class Meta:

    fields = (
        'id',
        'sku',
        "title",
        "old_price",
        "price",
        "sale",
        "images",
        "original_categories",
        "related_color",
        "original_conv_color",
        "original_sizes",
    )
    model = Products

@staticmethod
def setup_eager_loading(queryset):
    queryset = queryset.only('id', 'sku', 'title', 'old_price', 'price', 'sale', 'original_categories', 'original_conv_color', 'original_sizes').prefetch_related('images', 'related_color')

    return queryset

查看

class ProductsViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Products.objects.all()
permission_classes = [DjangoModelPermissionsOrAnonReadOnly]
filter_backends = (filters.SearchFilter, DjangoFilterBackend, filters.OrderingFilter, CustomFilter, SizeFilter)
filter_fields = ('slug', 'code', 'sku', 'color', 'attributes', 'category', 'original_color')
min_max_fields = ('price', 'sale')
search_fields = ('title', 'original_color', 'original_categories', 'original_conv_color', 'original_sizes')
ordering_fields = ('sale', 'price', 'created_at')
pagination_class = StandardResultsSetPagination

def get_queryset(self):
    if self.action == 'list':
        queryset = self.get_serializer_class().setup_eager_loading(self.queryset.filter(deleted=0,in_stock=1))
        return queryset
    return self.queryset

def get_serializer_class(self):
    if self.action == 'list':
        return ProductsListSerializer
    if self.action == 'retrieve':
        return ProductsSerializer
    return ProductsSerializer

【问题讨论】:

【参考方案1】:

只是一个建议 查看您的查询代码 确保您在

上有适当的复合索引
table catalog_products  index  on (deleted, in_stock, id )
table catalog_products_category index  on  ( categories_id, products_id, id  )

并避免在代码周围出现无用的 () ..

SELECT COUNT(*) 
FROM (
  SELECT DISTINCT `catalog_products`.`id` AS Col1 
  FROM `catalog_products` 
  INNER JOIN `catalog_products_category` 
    ON `catalog_products`.`id` = `catalog_products_category`.`products_id` 
  WHERE `catalog_products`.`deleted` = 0 
  AND `catalog_products`.`in_stock` = 1 
  AND `catalog_products_category`.`categories_id` = 183
  ) subquery


SELECT DISTINCT `catalog_products`.`id`
  , `catalog_products`.`sku`
  , `catalog_products`.`title`
  , `catalog_products`.`old_price`
  , `catalog_products`.`price`
  , `catalog_products`.`sale`
  , `catalog_products`.`original_categories`
  , `catalog_products`.`original_conv_color`
  , `catalog_products`.`original_sizes` 
FROM `catalog_products` 
INNER JOIN `catalog_products_category` 
  ON `catalog_products`.`id` = `catalog_products_category`.`products_id`
WHERE `catalog_products`.`deleted` = 0 
AND `catalog_products`.`in_stock` = 1 
AND `catalog_products_category`.`categories_id` = 183 
ORDER BY `catalog_products`.`title` ASC LIMIT 48

最后的建议记住,order by 对排序有相当大的影响,然而,对结果引入限制的事实意味着,必须选择、排序和最终提取所有行,仅基于数量在限制处。

【讨论】:

感谢您的回复,我已经在 catalog_products.id 上索引了必需的列,我在 catalog_products_category(id、products_id、类别 id)上索引了“Primary”(主要、唯一、索引)。请让我知道这是否可以,或者我应该在所有这些上都有索引类型“索引”。括号也出现在我的脑海中,但似乎它们没有造成任何伤害,到目前为止使用括号的查询更快我不知道为什么,因为我也认为它应该是其他方式。 向我展示您的表架构 .. 您在评论中提到的索引对我来说似乎不是最佳的 再次感谢您的回复。当您提到索引时,我想通了,我仔细检查了我的查询和数据库结构,所有索引都在正确的位置,但是如果您注意到我在查询中按标题排序。这一直在这里花费。感谢您快速回复。 如果您可以按照我的解释更新答案,我会接受您的答案,否则我必须为看到此问题的其他人写一个。 @ShanKhaliq .. 回答更新,提供一些关于订购和限制成本的建议【参考方案2】:

您的查询的优化似乎很有可能——老实说。我确定这是使用正确索引的问题。

我不知道每个表上列选择性的所有详细信息(这是必不可少的),所以我假设,例如,categories_id = 183 实际上会过滤掉大部分行;我可能错了。我将假设所有相关表(catalog_products_categorycatalog_products_colorcatalog_sizethrough)都具有类似的选择性。

如果是这样,那么我会推荐以下索引来加快搜索速度:

create index ix1 on catalog_products_category (categories_id, products_id);
create index ix2 on catalog_products_color (color_id, products_id);
create index ix3 on catalog_sizethrough (size_id, stock, products_id);
create index ix4 on catalog_products (deleted, in_stock, id);

试一试。如果您的查询仍然很慢,请发布最慢的执行计划进行解释。

【讨论】:

以上是关于使用 Django Rest 框架的 Mysql 多连接性能的主要内容,如果未能解决你的问题,请参考以下文章

Django + AngularJS:没有使用普通 URL 和视图的 Django REST 框架的类 REST 端点?

使用 django-allauth 和 django-rest 框架

Django rest框架认证测试

django.test.client 上的 Django rest 框架导入错误

Django Rest 框架的 ModuleNotFoundError

Django Social登录/注册使用rest框架