Django REST API医生预约系统并发请求处理及预约顺序判定技术咨询
关于Django REST API并发预约的问题解答
嘿,作为Django新手碰到这个并发问题太正常了——你的担心完全正确,确实存在多个用户同时发起相同预约请求的可能,这种情况在热门医生的预约场景下很容易触发竞态条件(Race Condition),会导致两个核心问题:
- 超过医生的单日预约上限(比如两个请求同时读到
date_counter为4,而医生上限是5,结果都创建了第5个预约,最终变成6个) - 两个用户得到相同的就诊编号,破坏了就诊顺序的唯一性
为什么你的现有代码会有问题?
你当前的代码是先读取数据库里的预约数量,再判断是否可以创建新预约,但这个“读-判断-写”的过程不是原子的:当两个请求同时执行到len(Appointment.objects.filter(doctor, date))时,它们会读到相同的计数,然后都通过判断并创建预约,最终导致数据不一致。
解决方案:用数据库原子操作+行锁保证一致性
要解决这个问题,核心是把预约创建的整个流程变成原子操作,让数据库帮你锁定资源,避免并发冲突。下面是具体的改进方案:
1. 给Appointment模型加唯一约束(强制用户不能重复预约)
先在你的Appointment模型里添加唯一约束,从数据库层面拦截同一个用户给同一医生同一天的重复预约:
class Appointment(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE) date = models.DateField() patient_number = models.IntegerField() # 就诊顺序号 class Meta: # 确保同一用户-医生-日期组合唯一 unique_together = ('user', 'doctor', 'date')
2. 修改视图函数,用原子事务+行锁处理并发
把预约创建的逻辑放在transaction.atomic()块里,并用select_for_update()锁定该医生当天的所有预约记录,确保同一时间只有一个请求能修改这个数据集:
from django.db import transaction, IntegrityError from django.db.models import Max from django.utils import timezone def post(self, request, did): doctor = get_object_or_404(Doctor, pk=did) # 先处理日期格式,避免解析错误 try: appointment_dt = timezone.datetime.strptime(request.GET['date'], '%Y-%m-%d').date() except (KeyError, ValueError): return Response( {'date': 'Invalid date format, please use YYYY-MM-DD'}, status=status.HTTP_400_BAD_REQUEST ) # 先判断医生当天是否出诊 if not doctor.is_available(appointment_dt): return Response( {'date':'the doctor is not available at this date'}, status=status.HTTP_409_CONFLICT ) try: with transaction.atomic(): # 锁定该医生当天的所有预约,防止其他并发请求修改 daily_appointments = Appointment.objects.filter( doctor=doctor, date=appointment_dt ).select_for_update() # 原子性获取当前预约数量 current_count = daily_appointments.count() if current_count >= doctor.patient_limit: return Response( {'patient_number':'the date is full'}, status=status.HTTP_406_NOT_ACCEPTABLE ) # 原子性计算下一个就诊编号:取当前最大编号+1,没有则从1开始 max_number = daily_appointments.aggregate(max_num=Max('patient_number'))['max_num'] or 0 patient_number = max_number + 1 # 创建并保存预约 appointment = Appointment( user=request.user, doctor=doctor, date=appointment_dt, patient_number=patient_number, # 其他字段从请求中获取,注意过滤掉不需要的参数 **{k: v for k, v in request.GET.items() if k not in ['date']} ) appointment.save() # 返回成功响应 return Response( { 'id': appointment.id, 'patient_number': patient_number, 'date': appointment_dt.strftime('%Y-%m-%d'), 'doctor_id': did }, status=status.HTTP_201_CREATED ) except IntegrityError: # 捕获唯一约束冲突(用户重复预约)或数据库锁冲突 return Response( {'error': 'Concurrent request detected, please try again later'}, status=status.HTTP_409_CONFLICT )
额外优化建议
- 前端防重复提交:在用户点击预约按钮后,立即禁用按钮并显示加载状态,避免用户重复点击发起多个请求。
- 高并发场景可选Redis:如果你的API流量很大,可以用Redis的
INCR命令原子生成就诊编号(按doctor_id:date作为key),性能比数据库锁更好,但需要额外配置Redis。 - 日志监控:添加日志记录并发冲突的情况,方便后续排查问题。
内容的提问来源于stack exchange,提问作者th3plus




