Django的ORM在处理多表关联时非常强大,但不少人在写了一段时间正向查询之后,第一次接触反向查询时会感到困惑:明明模型之间有外键关联,为什么没法用属性直接访问关联数据?其实反向查询有两种完全不同的写法,一种依赖默认的_set管理器,另一种则由外键字段上的related_name参数决定。理解这两者的关系,是掌握Django多表查询绕不开的一步。

什么是反向查询,它与正向查询的区别在哪里
先看一个最典型的场景:作者和书籍。一个作者可以写多本书,书籍模型通过外键指向作者模型。从书籍找作者是正向查询,因为外键就定义在书籍模型上;从作者找他写的所有书则是反向查询,因为作者模型上并没有显式定义任何关联字段,Django是在运行时动态创建访问入口的。
正向查询的写法很直观,直接点外键字段名即可:
class Author(models.Model):
name = models.CharField(max_length=50)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
# 正向查询:通过书找作者
book = Book.objects.get(id=1)
print(book.author.name)
而反向查询不能写author.book,因为Author模型上根本不存在book这个字段。Django的规则是:默认情况下,在关联模型实例上通过小写模型名加_set的形式访问,也就是book_set。它本质上是一个RelatedManager,行为和普通的objects管理器类似,支持all、filter、count等方法。
# 反向查询:通过作者找所有书 author = Author.objects.get(id=1) books = author.book_set.all() print(books.count()) # 也可以直接过滤 python_books = author.book_set.filter(title__contains='Python')
需要注意一点,book_set中的book是模型名的小写形式。如果模型名由多个单词组成,比如BookChapter,默认的反向名称就是bookchapter_set,模型名会被转成小写再拼接_set。这一点经常被人忽略,导致在模板或视图中写出BookChapter_set这种大小写不一致的写法而报AttributeError。
related_name参数:自定义反向查询的访问名称
如果觉得book_set这种命名不够直观,可以在定义外键时通过related_name参数指定一个更语义化的名称。设置之后,原来的_set写法就完全失效,只能使用你指定的名称访问。
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(
Author,
on_delete=models.CASCADE,
related_name='books'
)
# 使用自定义名称反向查询
author = Author.objects.get(id=1)
all_books = author.books.all()
recent_books = author.books.filter(create_time__year=2024)
related_name的命名建议用复数形式,因为它返回的是一个集合。比如作者的书用books,分类下的商品用products,这样代码读起来更接近自然语言:author.books.all()一眼就能看出是取某作者的全部书籍。
还有一个特殊的用法值得单独说明:把related_name设置成加号(+)可以彻底关闭反向查询入口。这在某些业务场景下很有用,比如日志表通过外键关联用户表,但你永远不需要从用户反向查日志,加上related_name='+'之后,User实例上就不会创建log_set管理器,避免误用。
class OperationLog(models.Model):
user = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE,
related_name='+'
)
# 此时user.operationlog_set 会直接报错
另外,当同一个模型被另一个模型多次引用时,related_name就不再是可选项而是必填项。例如文章模型同时有作者和编辑两个外键都指向User,如果不分别指定related_name,Django在做模型检查时会抛出冲突错误,提示反向查询名称重复。正确写法如下:
class Article(models.Model):
author = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE,
related_name='authored_articles'
)
editor = models.ForeignKey(
'auth.User',
on_delete=models.SET_NULL,
null=True,
related_name='edited_articles'
)
在filter和序列化中运用反向查询
反向查询不仅能在实例上使用,在查询集的条件过滤中也扮演重要角色,而且语法不同。实例上用的是book_set,但在filter里要使用小写模型名(或related_name指定的名称),不需要加_set后缀。这是最容易混淆的地方,很多初学者会写成filter(book_set__title='xxx')然后报FieldError。
# 未设置related_name时,跨表过滤用小写模型名
authors = Author.objects.filter(book__title__contains='Django')
# 设置related_name='books'后,用自定义名称
authors = Author.objects.filter(books__title__contains='Django')
# 统计每个作者的书数量并排序
from django.db.models import Count
ranking = Author.objects.annotate(
total=Count('books')
).order_by('-total')
在Django REST Framework中,related_name的价值更加明显。如果想在作者接口中直接嵌套返回所有书籍,序列化器只需按反向名称声明一个字段,框架会自动处理嵌套输出:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', 'title']
class AuthorSerializer(serializers.ModelSerializer):
books = BookSerializer(many=True, read_only=True)
class Meta:
model = Author
fields = ['id', 'name', 'books']
这样请求作者详情时,返回的JSON里会自动带上一个books数组,无需在视图中手动组装数据。如果用的是默认的_set名称,也可以在序列化器里用source参数指定,写成BookSerializer(source='book_set', many=True, read_only=True),效果完全相同。
最后提醒一个性能问题:无论是book_set.all()还是序列化器里的嵌套读取,只要在循环中逐个访问都会触发N+1查询。解决办法是在查询入口处用prefetch_related预加载反向关联数据,例如Author.objects.prefetch_related('books'),它会把所有关联书籍一次性查出来放入缓存,循环访问时不再重复打数据库。正向外键用select_related,反向一对多用prefetch_related,这个搭配习惯建议在项目初期就固定下来。
Django反向查询related_name_set管理器修改时间:2026-09-11 09:42:37