Django缓存更新报错:'creditpayments' lookup关联不同queryset
我来帮你搞定这个问题!这个报错的核心原因是你缓存了已经带有prefetch_related标记的QuerySet,第二次取出时又重复对同一个关联字段做预取操作。Django会检测到同一个creditpayments关联被绑定了不同的QuerySet(第一次是你缓存时的,第二次是新创建的Prefetch对象),所以抛出了这个错误。
为什么会这样?
Django的QuerySet是惰性执行的,但当你调用cache.set时,为了序列化QuerySet,Django会触发它的求值(也就是执行数据库查询),同时会把预取关联的元信息也保存下来。当你第二次从缓存取出这个QuerySet,再调用prefetch_related(prefetch)时,就会和已有的预取信息冲突。
解决方案(推荐两种)
方案1:缓存原始QuerySet,每次请求时动态处理
把未经过任何预取、注解的原始Credit QuerySet存入缓存,每次AJAX请求时再基于这个干净的QuerySet做后续操作。这样就不会有重复预取的问题:
class SortedCreditsList(ListView): def get(self, *args, **kwargs): if self.request.is_ajax(): # 缓存原始的、未处理的QuerySet credits = cache.get('credits') if credits is None: credits = Credit.objects.all() cache.set('credits', credits) # 每次请求都重新做预取、注解、排序 prefetch = Prefetch('creditpayments', CreditPayment.objects.all()) processed_credits = credits.prefetch_related(prefetch).annotate(min_rate=Min('creditpayments__rate')) processed_credits = processed_credits.order_by('min_rate') # 这里不需要缓存处理后的QuerySet,而是缓存原始的 filtered_credits = processed_credits.filter(best=False, hot=False) template_ajax = render_to_string( template_name='credits/includes/credits_filter_result.html', context={'credits': filtered_credits,} ) return JsonResponse({'success': True, 'template': template_ajax})
方案2:缓存序列化后的对象数据(更稳定)
QuerySet本身并不适合长期缓存,因为它携带了查询的状态信息。更稳妥的方式是缓存具体的对象数据(比如字典列表),每次请求时从缓存取出数据,再转换成需要的格式:
from django.core import serializers from django.db.models import Min class SortedCreditsList(ListView): def get(self, *args, **kwargs): if self.request.is_ajax(): # 尝试从缓存取序列化后的数据 cached_data = cache.get('credits_data') if cached_data is None: # 首次查询时获取完整数据并序列化 credits = Credit.objects.prefetch_related('creditpayments').all() # 序列化QuerySet为JSON可序列化的格式 cached_data = serializers.serialize('json', credits) cache.set('credits_data', cached_data) # 反序列化数据为对象 credits = [obj.object for obj in serializers.deserialize('json', cached_data)] # 手动计算min_rate并排序(因为缓存的是原始对象,需要自己处理注解逻辑) for credit in credits: rate_aggregate = credit.creditpayments.aggregate(min_rate=Min('rate')) credit.min_rate = rate_aggregate['min_rate'] if rate_aggregate['min_rate'] else 0 # 排序和过滤 sorted_credits = sorted(credits, key=lambda x: x.min_rate) filtered_credits = [credit for credit in sorted_credits if not credit.best and not credit.hot] template_ajax = render_to_string( template_name='credits/includes/credits_filter_result.html', context={'credits': filtered_credits,} ) return JsonResponse({'success': True, 'template': template_ajax})
方案选择建议
你的业务需要更新缓存,方案1的改动更小,更贴近你原本的代码逻辑:如果需要更新缓存,只需要调用cache.delete('credits'),下次请求时就会重新拉取最新的原始QuerySet。而方案2如果需要更新,也要删除缓存的序列化数据,重新生成。
另外要记住:永远不要缓存经过复杂查询(带prefetch_related、annotate、filter等)的QuerySet,因为这些QuerySet携带的状态会导致后续操作冲突,而且QuerySet的序列化也可能带来意想不到的问题。
内容的提问来源于stack exchange,提问作者user11301070

