如何通过预取或缓存无关表优化Django的@property方法
优化方案
方案1:将ReferenceZscoreData加载到全局内存字典
由于该表每年仅更新一次,可将所有数据预加载到内存字典中,彻底避免重复查询数据库。
- 实现步骤:
- 在项目工具模块(比如
utils/zscore.py)中编写缓存加载函数:
from .models import ReferenceZscoreData _ZSCORE_DATA_CACHE = None def get_zscore_data_cache(): global _ZSCORE_DATA_CACHE if _ZSCORE_DATA_CACHE is None: # 一次性拉取所有数据,构建(sex, age)为键的映射字典 _ZSCORE_DATA_CACHE = { (item.sex, item.age): item for item in ReferenceZscoreData.objects.all() } return _ZSCORE_DATA_CACHE def clear_zscore_data_cache(): global _ZSCORE_DATA_CACHE _ZSCORE_DATA_CACHE = None- 修改
ChildBmiCheck的zscore属性:
from utils.zscore import get_zscore_data_cache @property def zscore(self): if self.child_was_present is True: age = self.child.age * 12 # 转换为月单位 gender = self.child.gender zscore_cache = get_zscore_data_cache() matching_data = zscore_cache.get((gender, age)) return self.calculate_z_score(matching_data) if matching_data else None else: return None- 当ReferenceZscoreData更新后,调用
clear_zscore_data_cache()清空缓存,确保新数据生效(可在更新数据的管理命令或后台视图中触发)。
- 在项目工具模块(比如
方案2:使用Django缓存框架缓存单条数据
利用Django自带的缓存系统,为每个(sex, age)组合设置长过期时间(比如1年),首次查询后直接从缓存读取。
- 实现步骤:
修改ChildBmiCheck的zscore属性:
注意:若使用数据库/文件缓存,需确保from django.core.cache import cache @property def zscore(self): if self.child_was_present is True: age = self.child.age * 12 # 转换为月单位 gender = self.child.gender cache_key = f"zscore_ref_{gender}_{age}" matching_data = cache.get(cache_key) if not matching_data: try: matching_data = ReferenceZscoreData.objects.filter(sex=gender).get(age=age) # 设置缓存有效期为1年(31536000秒) cache.set(cache_key, matching_data, 31536000) except ReferenceZscoreData.DoesNotExist: return None return self.calculate_z_score(matching_data) else: return NoneReferenceZscoreData对象可序列化;使用Redis等内存缓存则无需额外处理。数据更新时,可批量删除相关缓存键。
方案3:视图层批量预取所需数据
在DRF视图中,先收集当前请求所有需要的(sex, age)组合,批量查询ReferenceZscoreData后手动映射到每个ChildBmiCheck实例,彻底消除N+1查询问题。
- 实现步骤:
- 自定义序列化器和视图:
from rest_framework import serializers, viewsets from .models import ChildBmiCheck, ReferenceZscoreData class ChildBmiCheckSerializer(serializers.ModelSerializer): zscore = serializers.SerializerMethodField() def get_zscore(self, obj): if not obj.child_was_present: return None # 从上下文获取预取的映射数据 zscore_data_map = self.context.get('zscore_data_map', {}) key = (obj.child.gender, obj.child.age * 12) matching_data = zscore_data_map.get(key) return obj.calculate_z_score(matching_data) if matching_data else None class ChildBmiCheckViewSet(viewsets.ModelViewSet): serializer_class = ChildBmiCheckSerializer def get_queryset(self): # 先关联查询Child表,避免额外查询 queryset = ChildBmiCheck.objects.select_related('child').all() # 收集所有需要的(sex, age)组合 required_keys = set() for bmi_check in queryset: if bmi_check.child_was_present: age = bmi_check.child.age * 12 required_keys.add((bmi_check.child.gender, age)) # 批量查询ReferenceZscoreData zscore_data = ReferenceZscoreData.objects.filter( sex__in=[k[0] for k in required_keys], age__in=[k[1] for k in required_keys] ) # 构建映射字典 zscore_data_map = {(item.sex, item.age): item for item in zscore_data} # 将映射字典传入序列化器上下文 self.serializer_context['zscore_data_map'] = zscore_data_map return queryset
内容的提问来源于stack exchange,提问作者Bradleo
相关产品推荐
相关产品推荐

