Django中GET请求引发的竞态问题及修复方法咨询
问题修复方案
核心原因
你遇到的是竞态条件:
- 前端循环同步发起两个XHR请求,浏览器会并行处理,两个请求几乎同时到达后端
- Django默认每个请求对应独立事务,第一个请求的
save()操作还未提交到数据库时,第二个请求已经执行了Appointment.objects.all()查询,导致两次都读到旧数据
修复方案(前端+后端结合,确保彻底解决)
1. 前端:改为串行发起请求
确保第一个请求完成后再发起第二个,避免并行请求:
// 封装XHR为Promise function createAppointment() { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', "make_appointment/"); xhr.onload = () => resolve(xhr.response); xhr.onerror = reject; xhr.send(); }); } // 串行执行两次请求 async function createTwoAppointments() { for (let i = 0; i < 2; i++) { await createAppointment(); } } // 调用执行 createTwoAppointments();
2. 后端:添加并发控制(关键,防止其他场景的竞态)
即使前端串行,若有其他客户端同时调用该接口,仍可能出现问题,需在后端加事务锁:
from django.db import transaction from django.contrib.auth.decorators import login_required @login_required def make_appointment(request): # 开启原子事务,确保查询和保存操作是原子性的 with transaction.atomic(): # 使用select_for_update锁定所有Appointment记录,直到事务结束 # 这样其他请求必须等待当前事务完成才能查询 existing_count = Appointment.objects.select_for_update().count() print('existing_appointments = ', existing_count) new_appointment = Appointment() new_appointment.save() # 返回响应(比如重定向或JSON) return redirect('/some-page/')
简化替代方案(若仅需计数)
如果只是需要获取当前预约数量,无需查询所有记录,可直接用原子计数:
@login_required def make_appointment(request): existing_count = Appointment.objects.count() print('existing_appointments = ', existing_count) new_appointment = Appointment() new_appointment.save() # ...
注:
count()是原子操作,但如果你的业务逻辑依赖count()结果进行后续操作(比如设置预约序号),仍需用select_for_update确保原子性。
验证效果
修复后日志会符合预期:
[18/Dec/2024 17:42:48] "GET /make_appointment/ HTTP/1.1" 302 0 existing_appointments = 0 [18/Dec/2024 17:42:48] "GET /make_appointment/ HTTP/1.1" 302 0 existing_appointments = 1
内容的提问来源于stack exchange,提问作者marco
相关产品推荐
相关产品推荐

