Django开启ATOMIC_REQUESTS后连续API调用外键对象不存在问题求解
你遇到的偶发问题核心原因大概率是读写分离架构下的主从同步延迟,因为你开启了ATOMIC_REQUESTS配置,第一个创建Object1的接口只要正常返回ID,说明主库的事务已经提交完成,但是第二个创建订单的请求查询对象是否存在时走了还没同步到数据的从库,就会触发外键不存在的报错。以下是可行的解决方案:
解决方案1:强制关联对象查询走主库
在订单序列化器校验关联对象存在的逻辑中,指定查询使用主库,完全规避主从延迟的影响:
# OrderSerializer中新增校验逻辑 def validate_object1(self, value): try: # 假设主库的别名为default,强制走主库查询 Object1.objects.using("default").get(pk=value.id) except Object1.DoesNotExist: raise serializers.ValidationError("关联的对象不存在") return value
如果是传入的object1_id参数,校验逻辑修改为:
def validate_object1_id(self, value): if not Object1.objects.using("default").filter(pk=value).exists(): raise serializers.ValidationError("关联的对象不存在") return value
解决方案2:合并两个操作到同一个事务
直接重构现有逻辑,将创建草稿对象、创建订单两个步骤合并到同一个接口的同一个事务内,从根源上避免跨请求的对象可见性问题,该方案稳定性最高:
# 新增合并接口 class CreateDraftObjectWithOrderView(generics.GenericAPIView): serializer_class = DraftObjectWithOrderSerializer def post(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) validated_data = serializer.validated_data # 同一事务内先创建草稿对象 draft_obj = Object1.objects.create( **validated_data["object1_fields"], is_draft=True ) # 再创建关联订单 order = Order.objects.create( user=request.user, amount=validated_data["amount"], object1=draft_obj ) OrderService.call() return Response({ "object1_id": draft_obj.id, "order_id": order.id }, status=status.HTTP_201_CREATED)
解决方案3:增加创建订单的重试机制
如果不想改动现有接口的交互逻辑,可以给订单创建逻辑增加有限次数的重试,给主从同步预留缓冲时间:
from django.db.utils import IntegrityError import time class CreateOrderAPIView(generics.GenericAPIView): serializer = OrderSerializer MAX_RETRY_TIMES = 3 RETRY_INTERVAL = 0.1 # 每次重试间隔100ms def post(self, request, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) for retry_cnt in range(self.MAX_RETRY_TIMES): try: instance = serializer.save() break except IntegrityError as e: # 仅在关联object1不存在的报错时重试 if "object1_id" in str(e) and retry_cnt < self.MAX_RETRY_TIMES -1: time.sleep(self.RETRY_INTERVAL) continue raise OrderService.call() return Response({"order_id": instance.id})
排查验证建议
可以先排查是否存在读写分离配置,同时检查第一个创建Object1的接口是否存在静默捕获异常、事务回滚后仍返回ID的错误逻辑。
内容的提问来源于stack exchange,提问作者Ernst
相关产品推荐
相关产品推荐

