get() 抛 MultipleObjectsReturned 是因设计要求结果唯一,查出多条即报错;常见原因包括缺唯一约束、条件过宽或脏数据;应据确定性选 get() 或容错性选 filter().first()。

为什么 get() 会抛 MultipleObjectsReturned?
Django 的 get() 方法设计上就只接受「恰好一条」匹配结果。只要数据库里查出两条或以上,它立刻放弃、不妥协,直接抛 MultipleObjectsReturned 异常——这不是 bug,是它的契约。
常见触发场景包括:
- 忘记加唯一约束(比如
email字段没设unique=True,却用User.objects.get(email='x')) - 查询条件太宽泛(比如用
status='active'这种非唯一字段调get()) - 数据库脏数据(历史遗留的重复记录)
别指望它“返回第一条”或“静默截断”,Django 故意不给你这种模糊空间。
该用 get() 还是 filter().first()?
看你要的是「确定性」还是「容错性」:
- 用
get():你确信条件能唯一定位一条记录,且希望异常暴露问题(比如主键、唯一索引字段查询)。这是防御性编程。 - 用
filter().first():你接受「可能无结果」或「只取第一个」,比如按时间倒序找最新一条配置:Config.objects.filter(is_active=True).order_by('-updated_at').first()
注意两点:
-
filter().first()返回None而不是抛异常,需手动判空 - 它不保证原子性,如果并发写入,两次调用可能拿到不同对象(
get()同样不解决并发问题,但至少报错让你知道有歧义)
如何安全捕获并处理 MultipleObjectsReturned
直接 try/except 最直白,但关键在「之后做什么」:
- 不要裸吞异常(比如只写
except MultipleObjectsReturned: pass),必须明确业务意图 - 常见做法:
- 记录日志并告警(说明数据不一致,需人工核查)
- 回退到
filter()+ 显式处理多条逻辑(比如取最新、合并、或拒绝操作) - 抛出自定义业务异常,让上游决定重试或降级
示例:
from django.core.exceptions import MultipleObjectsReturned
<p>try:
user = User.objects.get(email=email)
except MultipleObjectsReturned:</p><h1>记录异常详情,方便排查</h1><pre class="brush:php;toolbar:false;">logger.error(f"Duplicate email found: {email}")
# 取最新注册的那个
user = User.objects.filter(email=email).order_by('-date_joined').first()
最容易被忽略的底层陷阱
get() 的「唯一性」完全依赖你写的查询条件,Django 不会主动检查字段是否真的唯一。哪怕模型里写了 unique=True,如果数据库迁移没生效(比如忘了 run makemigrations),或者用了 db_constraint=False,那 get() 照样会崩。
更隐蔽的是复合条件:Order.objects.get(user=user, status='pending') —— 即使 user 和 status 各自不唯一,组合起来也可能重复。这时得靠数据库层面的联合唯一索引兜底,否则异常永远只是时间问题。
真实项目里,这类异常往往在数据量上来、或清理脚本跑过之后才爆发。别等报错才补索引。










