Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

使用select_related和prefetch_related提高数据库查询效率 #264

Open
Time1ess opened this issue Jan 11, 2018 · 0 comments
Open

使用select_related和prefetch_related提高数据库查询效率 #264

Time1ess opened this issue Jan 11, 2018 · 0 comments

Comments

@Time1ess
Copy link
Member

根据django_debug_toolbar工具查询并分析,目前代码中存在的查询语句效率极其低下,如工艺模块的焊接接头工艺分析,对单个工艺分析的获取进行了52次SQL查询,消耗~82ms。主要原因在于serializer中多次使用类似obj.detail.weldingseam_set.count(),而该结果不会被Django缓存,导致大量SQL查询,该性能问题官方做出了解答:

If you are going to need other data from the QuerySet, just evaluate it.

class WeldingWorkInstructionSerializer(serializers.ModelSerializer):
bm_1 = serializers.SerializerMethodField()
bm_2 = serializers.SerializerMethodField()
bm_thick_1 = serializers.SerializerMethodField()
bm_thick_2 = serializers.SerializerMethodField()
wt_1 = serializers.SerializerMethodField()
wt_2 = serializers.SerializerMethodField()
work_order_uid = serializers.SerializerMethodField()
uid = serializers.SerializerMethodField()
weld_position = serializers.SerializerMethodField()
welding_work_instruction_index = serializers.SerializerMethodField(
read_only=True)
def get_welding_work_instruction_index(self, obj):
return str(obj)
def get_weld_position(self, obj):
return obj.detail.weldingseam_set.first().weld_position
def get_uid(self, obj):
return obj.detail.joint_index
def get_work_order_uid(self, obj):
return obj.detail.spec.work_order.uid
def get_bm_1(self, obj):
if obj.detail.weldingseam_set.count() > 0:
return obj.detail.weldingseam_set.first().bm_1
return None
def get_bm_2(self, obj):
if obj.detail.weldingseam_set.count() > 0:
return obj.detail.weldingseam_set.first().bm_2
return None
def get_bm_thick_1(self, obj):
if obj.detail.weldingseam_set.count() > 0:
return obj.detail.weldingseam_set.first().bm_thick_1
return None
def get_bm_thick_2(self, obj):
if obj.detail.weldingseam_set.count() > 0:
return obj.detail.weldingseam_set.first().bm_thick_1
return None
def get_wt_1(self, obj):
if obj.detail.weldingseam_set.count() > 0:
return obj.detail.weldingseam_set.first().wt_1
return None
def get_wt_2(self, obj):
if obj.detail.weldingseam_set.count() > 0:
return obj.detail.weldingseam_set.first().wt_2
return None
class Meta:
model = WeldingWorkInstruction
fields = '__all__'

select_related和prefetch_related

select_related和prefetch_related方法是Django提供的数据库查询优化方法,前者通过在数据库层面进行join操作减少查询数据所需的往返次数,后者通过关联关系预先获取当前查询集的外键信息,在Python层面进行'join',二者均能大幅提高性能,具体选用哪一种需要根据关联关系,select_related需要正向的外键关系,而prefetch_related同时支持正反向,但目前测试来看性能略低于select_related(可能由于数据量太小,数据库层面join操作耗时不多,有待研究)。

作为性能优化对比,以库存模块辅材领用台账为例:/api/auxiliary_material_apply_ledgers/

优化前

查询列表操作耗时随着对象数量的增加呈线性增长,直至达到单页最大数量后,查询时间不再增加,当达到每页10个(单页最大数量),此时的查询操作进行82次SQL查询,耗时~145.32ms,平均单个台账的查询耗时~14.53ms。
原API代码:

class AuxiliaryMaterialApplyLedgerViewSet(viewsets.ReadOnlyModelViewSet):
pagination_class = SmallResultsSetPagination
serializer_class = serializers.AuxiliaryMaterialApplyLedgerSerializer
queryset = AuxiliaryMaterialApplyCard.objects.all().order_by('-pk')
filter_class = filters.AuxiliaryMaterialApplyLedgerFilter

优化后

时间不再随数量线性增长,此时任意数量(以10个对象为例)的查询操作均进行2次SQL查询,耗时~11.16ms,平均单个台账的查询耗时~1.12ms。
新API代码:

class AuxiliaryMaterialApplyLedgerViewSet(viewsets.ReadOnlyModelViewSet):
pagination_class = SmallResultsSetPagination
serializer_class = serializers.AuxiliaryMaterialApplyLedgerSerializer
queryset = (AuxiliaryMaterialApplyCard.objects.all().order_by('-pk')
.select_related(
('apply_inventory__entry_detail__procurement_material'
'__process_material'),
('actual_inventory__entry_detail__procurement_material'
'__process_material')))
filter_class = filters.AuxiliaryMaterialApplyLedgerFilter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants