You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django3搭配MySQL时如何防范医生接诊场景的并发操作冲突?

是否会产生并发数据冲突

会。如果不做特殊的并发控制,3名医生同时点击认领按钮时完全可能出现数据异常,核心原因是常规的「先查询患者状态→判断是否为待接诊→执行更新认领」逻辑是典型的非原子"读-改-写"流程:

  • 3个并发请求会同时查询到Jennifer的状态为「待接诊」,全部通过业务校验
  • 后续的更新操作会依次执行,最终Jennifer的接诊医生字段会被最后一个提交的请求覆盖,甚至可能在接诊关联表写入多条重复的认领记录,完全违反"一个待接诊患者只能被一名医生认领"的业务规则。
可落地的规避方案(适配Django 3 + MySQL技术栈)

所有方案的核心思路都是把状态校验和更新操作合并为原子操作,从数据库层面拦截重复认领,不要只靠前端或者应用层的非原子判断做控制。

优先推荐:基于条件更新的乐观锁方案

这个方案代码最简洁、性能最好,完全适配接诊认领的业务场景,不需要显式加数据库锁,依靠MySQL更新操作本身的原子性实现并发控制。
核心逻辑是直接把「患者为待接诊状态」作为update语句的查询条件,通过数据库返回的影响行数判断是否认领成功,示例代码:

from django.db import transaction
from your_app.models import Patient, ConsultRecord

def claim_patient(request, patient_id):
    current_doctor = request.user.doctor
    with transaction.atomic():
        # 直接执行更新,where条件同时校验患者ID和待接诊状态
        updated_count = Patient.objects.filter(
            id=patient_id,
            status=Patient.Status.WAITING # 待接诊状态枚举值
        ).update(
            status=Patient.Status.CLAIMED,
            attending_doctor=current_doctor
        )

        if updated_count == 0:
            return JsonResponse({"code": 1, "msg": "该患者已被其他医生认领,请刷新列表"})
        
        # 认领成功后写入接诊记录等后续关联操作
        ConsultRecord.objects.create(
            patient_id=patient_id,
            doctor=current_doctor
        )
        return JsonResponse({"code": 0, "msg": "认领成功"})

MySQL执行单条update语句时会自动给匹配的行加写锁,同一时间永远只有一个请求能匹配到待接诊状态的目标患者,更新成功后其他并发请求的update语句匹配不到符合条件的行,影响行数为0,直接返回认领失败,从根本上避免重复认领。

备选方案:基于select_for_update的悲观锁方案

如果后续认领逻辑需要执行更多关联查询、多步数据校验,逻辑比较复杂,可以用Django提供的select_for_update()方法实现数据库行级排他锁,把整个认领流程包在事务里,同一时间只有一个事务能拿到目标患者行的锁,其他请求会阻塞等待锁释放,示例代码:

from django.db import transaction
from your_app.models import Patient

def claim_patient(request, patient_id):
    current_doctor = request.user.doctor
    try:
        with transaction.atomic():
            # 查询时加排他行锁,匹配不到符合条件的患者直接抛DoesNotExist
            patient = Patient.objects.select_for_update().get(
                id=patient_id,
                status=Patient.Status.WAITING
            )
            # 拿到锁之后再执行多步校验、更新操作
            patient.status = Patient.Status.CLAIMED
            patient.attending_doctor = current_doctor
            patient.save()
            # 其他关联操作...
            return JsonResponse({"code":0, "msg":"认领成功"})
    except Patient.DoesNotExist:
        return JsonResponse({"code":1, "msg":"该患者已被其他医生认领,请刷新列表"})

注意这个方案需要确保患者表使用InnoDB存储引擎,否则会锁全表;如果不想让请求阻塞等待锁,可以给select_for_update()传入skip_locked=True参数,拿不到锁的请求会直接返回空,不会排队,适合并发量更高的场景。

前端辅助优化(仅做体验提升,不能替代后端控制)

  • 医生点击认领按钮后立刻将按钮置灰,禁止重复提交
  • 待接诊列表设置合理的自动刷新间隔,及时移除已经被认领的患者
  • 认领接口返回失败时给用户明确提示,引导刷新列表
    注意:所有前端拦截都只是体验优化,数据正确性的保障100%依赖后端的原子化逻辑,绝对不能只靠前端状态判断做拦截。
必须避开的错误写法

下面这种无任何并发控制的写法是出现数据冲突的核心原因,绝对不要用:

# ❌ 错误示例,并发场景必出问题
def bad_claim_logic(request, patient_id):
    patient = Patient.objects.get(id=patient_id)
    # 多个并发请求会同时通过这个判断
    if patient.status == Patient.Status.WAITING:
        patient.attending_doctor = request.user.doctor
        patient.status = Patient.Status.CLAIMED
        # 最后执行save的请求会覆盖之前的所有更新
        patient.save()
    return JsonResponse({"msg":"操作完成"})

内容的提问来源于stack exchange,提问作者ivbtar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 04:15:30