登录
首页 >  文章 >  python教程

Python Django 实战:async view 里别直接摸同步 ORM

来源:Python 博主原创

时间:2026-06-04 13:48:48 310浏览 收藏

Django 现在支持异步视图,很多 Python 后端团队一升级到 ASGI,就想把接口全改成 async def。我支持这个方向,但不支持一把梭。最常见的事故是:异步视图里直接调用同步 ORM,开发环境偶尔报 SynchronousOnlyOperation,线上则表现成 P99 飙升、连接池吃紧、事件循环被拖住。

这篇文章不讲 Django 入门,也不搬官方 API 列表。我用一个订单详情页的排障过程说明:什么时候 async view 真有收益,ORM 怎么隔离,sync_to_async 怎么用,事务边界怎么收口,以及上线前我会检查哪些指标。

Python Django 异步视图治理思维导图
治理思路:ASGI 能提升并发等待能力,但 ORM、事务和同步中间件边界必须单独设计。

业务场景:异步详情页越改越慢

假设订单详情页需要同时取订单、调用会员服务、调用风控服务。团队把视图改成异步以后,外部 HTTP 调用确实可以并发等待,但代码里还保留了同步 ORM。

# views.py
async def order_detail(request, order_id: int):
    order = Order.objects.select_related("user").get(pk=order_id)
    risk = await risk_client.get_score(order.user_id)
    member = await member_client.get_profile(order.user_id)
    return JsonResponse({"order": order.no, "risk": risk, "member": member})

这段代码的问题不是“语法不 async”,而是边界混乱:异步视图运行在事件循环里,同步 ORM 访问会碰到 Django 的异步安全保护;即便绕过去,也可能把线程切换、连接复用和事务行为搞得很难预测。

先判断 async view 是否值得

我不会为了“看起来现代”改 async。Django async view 适合 I/O 等待多、可并发发起外部调用的接口,比如聚合多个 HTTP 服务、WebSocket、长轮询、轻量异步缓存访问。如果接口主要是 ORM 查询和模板渲染,同步视图反而更简单稳定。

Python Django ASGI 异步请求链路
请求链路:async view 可以承载非阻塞 I/O,但 ORM 和事务要经过清晰的同步边界。

上线前我会把接口分成三类:纯同步 ORM 密集型继续同步;外部 I/O 密集型考虑 async;混合型则把同步 ORM 封装到边界函数里,不让业务代码到处直接调用。

方案一:能用异步 ORM 就直接 await

Django 已经提供了一批异步 QuerySet 方法,比如 agetacreateaupdate 等。简单查询可以直接用异步 ORM 表达,代码边界最干净。

async def order_detail(request, order_id: int):
    order = await Order.objects.select_related("user").aget(pk=order_id)
    risk, member = await asyncio.gather(
        risk_client.get_score(order.user_id),
        member_client.get_profile(order.user_id),
    )
    return JsonResponse({"order": order.no, "risk": risk, "member": member})

这里的收益来自两个地方:ORM 调用遵守异步边界,两个外部服务可以并发等待。注意,asyncio.gather 不是越多越好,下游服务和连接池都有容量,生产里仍然要配超时、限流和熔断策略。

方案二:复杂 ORM 放进同步函数,再用 sync_to_async 包起来

现实项目里,很多查询不是一行 aget 能解决。比如带事务、复杂预取、历史库兼容、老代码复用。这个时候我更愿意把 ORM 逻辑封装成同步函数,再在 async view 里用 sync_to_async 调用。

from asgiref.sync import sync_to_async

def load_order_snapshot(order_id: int) -> dict:
    order = (
        Order.objects
        .select_related("user")
        .prefetch_related("items")
        .get(pk=order_id)
    )
    return {"no": order.no, "user_id": order.user_id, "item_count": len(order.items.all())}

async def order_detail(request, order_id: int):
    snapshot = await sync_to_async(load_order_snapshot, thread_sensitive=True)(order_id)
    risk = await risk_client.get_score(snapshot["user_id"])
    return JsonResponse({"order": snapshot, "risk": risk})

我喜欢这种写法,因为它把“同步世界”和“异步世界”分得很清楚。视图负责异步编排,函数负责 ORM 读取。出了问题也容易定位:到底是数据库慢,还是外部服务慢。

Python Django async view ORM 边界代码对照
代码审查重点:异步视图直接调用同步 ORM 是红线;复杂 ORM 应封装到同步边界里。

事务别跨过 await

我见过最危险的写法,是在事务里混入外部异步调用。事务打开后等待下游 HTTP,数据库连接被占住,锁时间变长,一旦下游抖动,数据库也跟着遭殃。

# 不推荐:事务边界里等待外部服务
async def pay_callback(request):
    with transaction.atomic():
        order = Order.objects.get(no=request.POST["order_no"])
        result = await payment_client.confirm(order.no)
        order.status = result.status
        order.save()

更稳的做法是把事务内的数据库更新封装到同步函数,外部调用放在事务外。需要强一致时,用状态机、幂等键和补偿任务来兜底,不要用一个跨网络的长事务硬扛。

def mark_paid(order_no: str, status: str) -> None:
    with transaction.atomic():
        order = Order.objects.select_for_update().get(no=order_no)
        order.status = status
        order.save(update_fields=["status"])

async def pay_callback(request):
    result = await payment_client.confirm(request.POST["order_no"])
    await sync_to_async(mark_paid, thread_sensitive=True)(request.POST["order_no"], result.status)
    return JsonResponse({"ok": True})

诊断步骤:先找同步边界泄漏

Django async 改造出问题时,我通常这样查:

  • 检查 async view 里是否直接出现 Model.objects.getfiltersavetransaction.atomic
  • 检查同步中间件是否让请求在 sync/async 之间频繁切换。
  • 按接口记录 P95/P99、数据库查询耗时、外部服务耗时和线程池排队时间。
  • 压测混合流量:一个慢外部服务是否拖慢纯数据库接口。
  • 确认数据库连接数、事务持续时间和慢查询是否同步上升。

上线检查清单

  • 只有 I/O 等待明显的接口才改 async view,ORM 密集型接口保持同步也可以。
  • async view 中禁止直接调用同步 ORM,简单查询优先用异步 ORM 方法。
  • 复杂 ORM 和事务逻辑封装成同步函数,用 sync_to_async(..., thread_sensitive=True) 调用。
  • 事务边界内不等待外部 HTTP,不把数据库锁交给下游服务质量决定。
  • 同步中间件逐个盘点,避免 ASGI 下频繁上下文切换。
  • 压测观察 P99、线程池等待、数据库连接数、事务时长、异常日志。
  • 保留回滚到同步视图或 WSGI 路径的方案,先灰度非核心接口。

总结

Django 的 async 支持不是“把 def 改成 async def”这么简单。它真正考验的是边界感:哪里可以异步等待,哪里必须同步收口,哪里要用线程敏感模式保护 ORM,哪里不能让事务跨过网络调用。

我的经验是,Django async 改造要小步走。先挑外部 I/O 明显的接口,画清 ORM 边界,压测指标看懂以后再扩大范围。这样 async view 才是生产优化,而不是给老系统添一层新的不确定性。

声明:本文转载于:Python 博主原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>