使用 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.83
ms
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.52
ms
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.63
ms
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.43
ms
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_category
、catalog_products_color
和 catalog_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.test.client 上的 Django rest 框架导入错误