在Django项目开发过程中,数据库查询的性能优化是绕不开的话题,很多时候我们编写查询逻辑时,默认会获取模型的所有字段,但实际上部分场景下只需要用到其中几个字段,全字段查询会额外消耗数据库IO和内存资源。Django ORM提供的defer和only方法,就是专门用来优化这类场景的,能够让我们按需指定需要查询或者延迟查询的字段,从而降低查询开销。

defer方法的使用
defer方法的作用是延迟加载指定的字段,也就是说,当我们使用defer指定某些字段后,这些字段不会在首次查询时从数据库中获取,只有当后续实际访问这些字段的时候,才会单独发起查询获取对应的值。
假设我们有一个用户模型UserProfile,包含id、username、email、avatar、bio等多个字段,很多时候我们只需要展示用户名,不需要获取头像和个人简介这些大字段,就可以用defer延迟这两个字段的查询。
# 定义用户模型
from django.db import models
class UserProfile(models.Model):
username = models.CharField(max_length=50)
email = models.EmailField()
avatar = models.ImageField(upload_to='avatar/')
bio = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
# 延迟查询avatar和bio字段
users = UserProfile.objects.defer('avatar', 'bio')
for user in users:
# 此时访问username和email不会触发额外查询
print(user.username, user.email)
# 访问avatar时会触发额外的数据库查询获取该字段的值
# print(user.avatar)
defer方法的注意事项
- defer可以接收多个字段名作为参数,同时延迟多个字段的加载。
- 如果后续访问了被延迟的字段,Django会单独发起一次查询获取该字段,所以如果确定后续不会用到这些字段,用defer才有优化效果。
- 不能延迟主键字段,因为Django ORM需要通过主键来标识模型实例,延迟主键会导致查询异常。
only方法的使用
only方法和defer的作用相反,它是指定只查询哪些字段,没有在only参数中指定的字段都会被延迟加载,只有后续访问的时候才会单独查询。
还是以上面的UserProfile模型为例,如果我们只需要用户名和邮箱两个字段,就可以用only指定这两个字段,其他字段都会被延迟。
# 只查询username和email字段,其他字段延迟加载
users = UserProfile.objects.only('username', 'email')
for user in users:
# 访问指定字段不会触发额外查询
print(user.username, user.email)
# 访问未指定的字段会触发额外查询
# print(user.bio)
only方法的注意事项
- only中指定的字段会立即查询,其他字段默认延迟,和defer的逻辑是互补的。
- 如果同时使用了only和defer,defer指定的字段会覆盖only中的对应字段,也就是defer指定的字段即使被only包含,也会被延迟。
- 同样不能指定主键字段,主键默认会被查询,不需要额外加到only参数中。
defer和only的区别与选择
两者的核心区别在于指定字段的逻辑不同:defer是指定哪些字段不查,only是指定哪些字段要查,其他字段都不查。在实际开发中可以根据场景选择:
- 如果大部分字段都需要用到,只有少数几个字段不需要,优先用defer,比如模型有很多字段,只需要排除两三个大字段,用defer更方便。
- 如果只需要用到少数几个字段,大部分字段都不需要,优先用only,比如只需要展示两个字段,用only更直观。
我们可以通过打印查询的SQL语句来验证两者的效果,Django可以通过queryset.query查看生成的SQL:
# 查看defer生成的SQL
defer_qs = UserProfile.objects.defer('avatar', 'bio')
print(defer_qs.query)
# 输出的SQL会包含除了avatar和bio之外的所有字段
# 查看only生成的SQL
only_qs = UserProfile.objects.only('username', 'email')
print(only_qs.query)
# 输出的SQL只会查询id、username、email三个字段,id是主键默认包含
使用场景总结
defer和only适合用在以下场景:
- 模型字段较多,且部分字段是大字段(比如长文本、图片字段),查询时不需要用到这些大字段,用defer排除。
- 列表页展示只需要少量字段,比如用户列表只需要用户名和注册时间,用only指定这两个字段即可。
- 避免N+1查询的辅助优化,比如关联查询时,主表只需要少量字段,可以配合only减少查询的字段数量。
需要注意的是,这两个方法只是减少单次查询的字段数量,不会减少查询的次数,如果后续频繁访问被延迟的字段,反而会增加查询次数,所以一定要根据实际业务场景判断是否真的不会用到被延迟的字段,再决定是否使用这两个优化方法。