DRF更新字段后接口返回旧数据需重启服务才生效问题排查
问题现象
发送PATCH请求更新Patient记录时,数据已成功写入数据库,Django Admin管理后台可查看到最新内容,但接口响应始终返回旧数据,必须重启服务后才能获取到更新后的字段值。
相关代码
Patient Model
class Patient(models.Model): user = models.OneToOneField( settings.AUTH_USER_MODEL, on_delete=models.CASCADE ) firstName = models.CharField(max_length=255) lastName = models.CharField(max_length=255) birthdate = models.DateField(auto_now_add=False, null=True, blank=True) nationalCode = models.CharField(max_length=10) address = models.TextField(blank=True) cellphone = models.CharField(max_length=15, blank=True) haveInsurance = models.BooleanField(default=False) insuranceNumber = models.CharField(max_length=20, blank=True) image = models.ImageField(null=True,blank=True, upload_to=patient_image_file_path) patientHistory = models.ForeignKey( 'PatientHistory', on_delete=models.CASCADE, blank=True, null=True) def __str__(self): return self.firstName + ' ' + self.lastName
Patient ViewSet
class PatientViewSet(viewsets.ModelViewSet): """viewset for listing and creating patients""" serializer_class = serializers.PatientSerializer queryset = Patient.objects.all().order_by('-lastName') authentication_classes = (TokenAuthentication,) permission_classes = (IsAdminUser | IsObjectOwner,) def get_queryset(self): firstName = self.request.query_params.get('first_name') lastName = self.request.query_params.get('last_name') insuranceNumber = self.request.query_params.get('insurance_number') cellphone = self.request.query_params.get('cellphone') queryset = self.queryset if firstName: queryset = queryset.filter(firstName=firstName) if lastName: queryset = queryset.filter(lastName=lastName) if insuranceNumber: queryset = queryset.filter(insuranceNumber=insuranceNumber) if cellphone: queryset = queryset.filter(cellphone=cellphone) return queryset def perform_create(self, serializer): if self.request.user.is_staff: email = self.request.data['email'] cellphone = self.request.data['cellphone'] name = self.request.data['firstName'] + " " + self.request.data['lastName'] if get_user_model().objects.filter(email=email).exists(): user = get_user_model().objects.get(email=email) else: user = get_user_model().objects.create_user( email=email, password=cellphone, name=name ) else: user = self.request.user haveInsurance = True if 'insuranceNumber' in self.request.data else False user.is_patient = True user.save() serializer.save( user=user, haveInsurance=haveInsurance )
Patient Serializer
class PatientSerializer(serializers.ModelSerializer): """Serializer for Patient objects""" patientHistory = serializers.SerializerMethodField() class Meta: model = Patient fields = [ 'id', 'firstName', 'lastName', 'birthdate', 'nationalCode', 'address', 'cellphone', 'haveInsurance', 'insuranceNumber', 'patientHistory', 'image' ] read_only_fields = ['id'] def get_patientHistory(self, obj): patientHistory = obj.patientHistory return patientHistory
根因定位
问题出在ViewSet的get_queryset方法实现上:
- 你在类属性层面定义了
queryset = Patient.objects.all().order_by('-lastName'),这个QuerySet实例是类级别全局共享的,在服务启动类加载时就会被创建。 get_queryset方法中直接queryset = self.queryset引用了这个共享的QuerySet实例。Django的QuerySet在第一次被迭代求值后,会把查询结果缓存到实例内部的_cache属性中,后续所有复用这个实例的操作都会直接读取缓存结果,不会重新查询数据库。- 第一次接口请求触发查询后,缓存就被填充,后续不管数据库里的数据怎么更新,接口都会返回缓存里的旧数据,直到服务重启,类重新加载、QuerySet实例重建,缓存才会被清空。
- 不需要额外编写update方法,也不需要配置所谓的「数据实时重载」,本质就是QuerySet实例被全局共享导致的进程内内存缓存问题。
修复方案
修改get_queryset方法,每次请求都生成全新的QuerySet实例,不要复用类属性上的共享QuerySet:
def get_queryset(self): firstName = self.request.query_params.get('first_name') lastName = self.request.query_params.get('last_name') insuranceNumber = self.request.query_params.get('insurance_number') cellphone = self.request.query_params.get('cellphone') # 每次请求新建QuerySet实例,避免复用历史缓存 queryset = Patient.objects.all().order_by('-lastName') if firstName: queryset = queryset.filter(firstName=firstName) if lastName: queryset = queryset.filter(lastName=lastName) if insuranceNumber: queryset = queryset.filter(insuranceNumber=insuranceNumber) if cellphone: queryset = queryset.filter(cellphone=cellphone) return queryset
如果要保留类属性的queryset定义(供DRF内置的权限检查、元数据解析等逻辑使用),可以在引用时调用.all()生成全新副本,同样可以避免缓存问题:
def get_queryset(self): # ... 省略参数获取逻辑 queryset = self.queryset.all() # 生成无缓存的新QuerySet副本 # ... 省略后续过滤逻辑
内容的提问来源于stack exchange,提问作者Ehsan Fouladi
相关产品推荐
相关产品推荐

