process_exception中间件常漏捕orm异常,因其仅捕获已进入视图函数体后抛出的异常;appregistrynotready、fielddoesnotexist等发生在启动或导入阶段,databaseerror在atomic块中可能被事务层静默吞掉,均无法触发该方法。

ORM数据库操作异常不能靠全局中间件“一锅端”捕获——它多数发生在视图执行前或事务上下文中,必须分层拦截。
为什么process_exception中间件常漏掉ORM异常
因为很多 ORM 异常根本不会走到 process_exception:比如 AppRegistryNotReady 发生在 Django 启动时模型未加载完;FieldDoesNotExist 可能在 models.py 导入阶段就抛出;而 DatabaseError 子类(如 OperationalError、IntegrityError)若出现在 atomic() 块里,会由事务层直接回滚并可能被静默吞掉。
-
process_exception只对“已进入视图函数体、但视图内部抛出的异常”生效 - URL 解析失败、中间件自身异常(如
DisallowedHost)、模型元数据访问错误,都不会触发它 - 异步视图中,
process_exception不被调用(Django 5+ 默认行为)
必须在三个位置分别设防
ORM 异常得按发生时机分层处理,缺一不可:
-
启动期:在
ready()钩子或AppConfig.ready中提前验证模型字段、关系,捕获FieldDoesNotExist、AppRegistryNotReady -
查询期:所有
get()调用必须配try/except ObjectDoesNotExist;filter().first()比get()更安全,不抛异常 -
写入期:在
atomic()块外显式 catchdjango.db.IntegrityError、django.db.OperationalError,避免事务自动回滚后无日志
常见 ORM 异常对应处理方式
不同异常语义不同,不能全塞进 500:
-
ObjectDoesNotExist→ 视为业务正常分支,返回JsonResponse({"code": 404, "msg": "not found"}, status=404) -
MultipleObjectsReturned→ 属于数据脏或逻辑错,记录 ERROR 日志 + 返回 500,**不要静默取第一个** -
IntegrityError(如唯一约束冲突)→ 映射为 400,提示“该用户名已被注册”,而非暴露 SQL -
OperationalError(如连接超时)→ 区分场景:重试 3 次后仍失败才返回 503,否则应静默重试 -
EmptyResultSet/FullResultSet→ 仅在自定义表达式中出现,普通项目基本不用管
别依赖 get() 的异常兜底,改用更可控的模式
get() 是最易引发线上崩溃的 ORM 方法之一。它把“查不到”和“查到多个”都转成异常,但实际业务中这两者处理逻辑完全不同:
- 查不到:通常是前端传参错误,应快速失败并明确提示
- 查到多个:大概率是数据库主键/索引缺失,属于严重数据问题,需告警而非返回 404
推荐统一用 filter().select_related(...).first() 替代 get(),再手动判断 is None;对“必须存在”的场景(如用户登录态),用 get_object_or_404() 并确保前端参数已校验。
真正难处理的从来不是异常类型本身,而是异常发生时的上下文是否完整——比如事务已提交一半、缓存已更新但 DB 写失败、信号已触发但主对象未保存。这些没法靠一个中间件解决,得靠每处 ORM 调用点的防御性编码和明确的 fallback 策略。











