Django基础: QuerySet特性及高级使用技巧,如何减少数据库的访问,节省内存,提升网站性能

本文探讨Django QuerySet的惰性加载与缓存机制,介绍如何通过exists、count、update及values等方法减少数据库访问,提升应用性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Django的QuerySet是惰性的

Django的QuerySet是惰性的。下例中article_list试图从数据库查询一个标题含有django的全部文章列表。

article_list = Article.objects.filter(title__contains=“django”)
但是当我们定义article_list的时候,Django的数据接口QuerySet并没有对数据库进行任何查询。无论你加多少过滤条件,Django都不会对数据库进行查询。只有当你需要对article_list做进一步运算时(比如打印出查询结果,判断是否存在,统计查询结果长度),Django才会真正执行对数据库的查询(见下例1)。这个过程被称为queryset的执行(evaluation)。Django这样设计的本意是尽量减少对数据库的无效操作,比如查询了结果而不用是计算资源的很大浪费。

example 1

for article in article_list:
   print(article.title)

Django的QuerySet自带缓存(Cache)

在例1中,当你遍历queryset(article_list)时,所有匹配的记录会从数据库获取。这些结果会载入内存并保存在queryset内置的cache中。这样如果你再次遍历或读取这个article_list时,Django就不需要重复查询了,这样也可以减少对数据库的查询。

下例中例2比例3要好,因为在你打印文章标题后,Django不仅执行了查询,还把查询到的article_list放在了缓存里。这个article_list是可以复用的。例3就不行了。

Example 2: Good

article_list = Article.objects.filter(title__contains=“django”)
for article in article_list:
   print(article.title)

Example 3: Bad

for article in Article.objects.filter(title__contains=“django”):
   print(article.title)

用if也会导致queryset的执行

不知道你注意到上述例2中有个问题没有?万一article_list是个空数据集呢? 虽然for…in…用到空集合上也不会出现raise什么错误,但专业优秀的我们怎么能允许这样的低级事情发生呢?最好的做法就是在loop前加个if判断(例4)。因为django会对执行过的queryset进行缓存(if也会导致queryset执行, 缓存article_list),所以我们在遍历article_list时不用担心Django会对数据库进行二次查询。

Example 4: Good

article_list = Article.objects.filter(title__contains=“django”)
if article_list:
   for article in article_list:
       print(article.title)
else:
   print(“No records”)
但有时我们只希望了解查询的结果是否存在,而不需要使用整个数据集,这时if触发整个queryset的缓存变成了一件坏事情。哎,程序员要担心的事情着不少。这时你可以用exists()方法。与if判断不同,exists只会检查查询结果是否存在,返回True或False,而不会缓存article_list(见例5)。

Example 5: Good

article_list = Article.objects.filter(title__contains=“django”)
if article_list.exists():
   print(“Records found.”)
else:
   print(“No records”)
注意: 判断查询结果是否存在到底用if还是exists取决于你是否希望缓存查询数据集复用,如果是用if,反之用exists。

统计查询结果数量优选count方法

len()与count()均能统计查询结果的数量。一般来说count更快,因为它是从数据库层面直接获取查询结果的数量,而不是返回整个数据集,而len会导致queryset的执行,需要将整个queryset载入内存后才能统计其长度。但事情也没有绝对,如果数据集queryset已经在缓存里了,使用len更快,因为它不需要跟数据库再次打交道。

下面三个例子中,只有例7最差,尽量不要用。

Example 6: Good

count = Article.objects.filter(title__contains=“django”).count()

Example 7:Bad

count = Article.objects.filter(title__contains=“django”).len()

Example 8: Good

article_list = Article.objects.filter(title__contains=“django”)
if article_list:
   print("{} records found.".format(article_list.len()))

当queryset非常大时,数据请按需去取

当查询到的queryset的非常大时,会大量占用内存(缓存)。我们可以使用values和value_list方法按需提取数据。比如例1中我们只需要打印文章标题,这时我们完全没有必要把每篇文章对象的全部信息都提取出来载入到内存中。我们可以做如下改进(例9)。

Example 9: Good

article_list = Article.objects.filter(title__contains=“django”).values(‘title’)
if article_list:
   print(article.title)

article_list = Article.objects.filter(title__contains=“django”).values_list(‘id’, ‘title’)
if article_list:
   print(article.title)
注意: values和values_list返回的是字典形式字符串数据,而不是对象集合。如果不理解请不要乱用。

另外还可以使用iterator()方法可以优化程序对内存的使用,其工作原理是不对queryset进行缓存,而是采用迭代方法逐一返回查询结果,但这有时会增加数据库的访问次数,新手一般也驾驭不了。我这里就不细讲了。

更新数据库部分字段请用update方法

如果需要对数据库中的某条已有数据或某些字段进行更新,更好的方式是用update,而不是save方法。我们现在可以对比下面两个案例。例10中需要把整个Article对象的数据(标题,正文…)先提取出来,缓存到内存中,变更信息后再写入数据库。而例11直接对标题做了更新,不需要把整个文章对象的数据载入内存,显然更高效。尽管单篇文章占用内存不多,但是万一用户非常多呢,那么占用的内存加起来也是很恐怖的。

Example 10: Bad

article = Article.objects.get(id=10)
Article.title = “Django”
article.save()

Example 11: Good

Article.objects.filter(id=10).update(title=‘Django’)
update方法还会返回已更新条目的数量,这点也非常有用。当然事情也没有绝对,save方法对于单个模型的更新还是很有优势的,比如save(commit=False), article.author = request.user等等事情update都做不来。

专业地使用explain方法

Django 2.1中QuerySet新增了explain方法,可以统计一个查询所消耗的执行时间。这可以帮助程序员更好地优化查询结果。

print(Blog.objects.filter(title=‘My Blog’).explain(verbose=True))

output

Seq Scan on public.blog  (cost=0.00…35.50 rows=10 width=12) (actual time=0.004…0.004 rows=10 loops=1)
 Output: id, title
 Filter: (blog.title = ‘My Blog’::bpchar)
Planning time: 0.064 ms
Execution time: 0.058 ms

小结

Django QuerySet的惰性和缓存特性对于减少数据库的访问次数非常有用。你需要根据不同应用场景选择合适的方法(比如exists, count, update, values) 来减少数据库的访问,减少查询结果占用的内存空间从而提升网站的性能。希望本文总结的一些高效使用queryset技巧对你学习Django和Web开发有所帮助。

参考

http://blog.etianen.com/blog/2013/06/08/django-querysets/

https://docs.djangoproject.com/en/2.1/ref/models/querysets/#django.db.models.query.QuerySet.values

大江狗

2018.8.11

作者:大江狗
来源:优快云
原文:https://blog.youkuaiyun.com/weixin_42134789/article/details/81626963
版权声明:本文为博主原创文章,转载请附上博文链接!

### Django ORM QuerySet 方法汇总及其功能详解 Django 的 ORM 提供了一套强大的工具来简化数据库的操作,其中 QuerySet 是核心概念之一。下面列举了常见的 QuerySet 方法,并对其功能进行了详细介绍。 --- #### 1. **查询方法** ##### (1) `filter(**kwargs)` - **功能**: 返回一个新的 QuerySet,包含满足给定条件的所有对象。 - **惰性执行**: 不会立即执行查询,直到实际使用数据时才会触发 SQL 请求。 - **示例**: ```python users = User.objects.filter(is_active=True) ``` [^1] ##### (2) `exclude(**kwargs)` - **功能**: 返回排除指定条件的对象集合。 - **特点**: 与 `filter` 相反,用于否定条件的筛选。 - **示例**: ```python inactive_users = User.objects.exclude(is_active=True) ``` ##### (3) `get(**kwargs)` - **功能**: 获取唯一匹配的对象。如果找到多个或找不到任何对象,则抛出异常。 - **注意**: 如果没有找到结果,会引发 `DoesNotExist`;如果有多个结果,会引发 `MultipleObjectsReturned`。 - **示例**: ```python user = User.objects.get(username='admin') ``` ##### (4) `first() / last()` - **功能**: 返回 QuerySet 中的第一个/最后一个对象。如果没有对象则返回 `None`。 - **示例**: ```python first_user = User.objects.first() last_user = User.objects.last() ``` --- #### 2. **聚合与统计方法** ##### (1) `count()` - **功能**: 计算 QuerySet 中的对象数量。 - **示例**: ```python total_users = User.objects.count() ``` ##### (2) `aggregate(*args, **kwargs)` - **功能**: 执行聚合计算(如求和、平均值等),返回字典形式的结果。 - **依赖模块**: 需要导入 `django.db.models` 下的相关函数。 - **示例**: ```python from django.db.models import Avg, Sum average_age = User.objects.aggregate(avg_age=Avg('age')) sum_salary = Employee.objects.aggregate(total_salary=Sum('salary')) ``` ##### (3) `exists()` - **功能**: 检查是否存在至少一个对象,通常用于优化布尔判断。 - **优点**: 性能优于 `bool(queryset)`。 - **示例**: ```python if User.objects.exists(): print("There are users.") ``` --- #### 3. **修改与更新方法** ##### (1) `update(field=value, ...)` - **功能**: 更新 QuerySet 中所有对象的一个或多个字段。 - **注意事项**: 此操作不会调用模型的 `save()` 方法,因此信号监听器不会生效。 - **示例**: ```python User.objects.filter(is_active=False).update(last_login=timezone.now()) ``` ##### (2) `create(**kwargs)` - **功能**: 创建并保存新的对象到数据库中。 - **示例**: ```python new_user = User.objects.create(username='test', email='test@example.com') ``` ##### (3) `bulk_create(objs, batch_size=None)` - **功能**: 批量创建多个对象,性能更高。 - **示例**: ```python objs = [User(username=f"user{i}") for i in range(10)] User.objects.bulk_create(objs) ``` ##### (4) `delete()` - **功能**: 删除 QuerySet 中的所有对象。 - **示例**: ```python User.objects.filter(is_active=False).delete() ``` --- #### 4. **序列化与转换方法** ##### (1) `values(*fields)` - **功能**: 返回只包含指定字段的字典列表。 - **示例**: ```python user_data = User.objects.values('username', 'email') ``` [^3] ##### (2) `values_list(*fields, flat=False)` - **功能**: 返回元组列表,当设置 `flat=True` 且只有一个字段时,返回单一值列表。 - **示例**: ```python emails = User.objects.values_list('email', flat=True) ``` ##### (3) `dates(field, kind, order='ASC')` - **功能**: 返回按日期分组的独特值。 - **示例**: ```python unique_dates = Entry.objects.dates('pub_date', 'day') ``` --- #### 5. **排序与切片方法** ##### (1) `order_by(*fields)` - **功能**: 根据指定字段对结果进行排序,默认升序,降序加 `-` 前缀。 - **示例**: ```python sorted_users = User.objects.order_by('-date_joined') ``` ##### (2) `reverse()` - **功能**: 反转当前 QuerySet 的顺序。 - **示例**: ```python reversed_users = User.objects.all().reverse() ``` ##### (3) `extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)` - **功能**: 支持自定义 SQL 查询片段。 - **示例**: ```python custom_ordering = Blog.objects.extra(order_by=['-rating']) ``` [^4] --- #### 6. **缓存与延迟加载** ##### (1) `iterator(chunk_size=2000)` - **功能**: 逐批迭代大 QuerySet节省内存占用。 - **示例**: ```python for user in User.objects.iterator(): process(user) ``` ##### (2) `defer(*fields)` - **功能**: 推迟某些字段的加载,减少初始查询的数据量。 - **示例**: ```python lazy_load = Entry.objects.defer("body") ``` ##### (3) `only(*fields)` - **功能**: 仅加载指定字段,其余字段在首次访问时再加载。 - **示例**: ```python optimized_load = Entry.objects.only("title", "author") ``` --- #### 7. **其他实用方法** ##### (1) `distinct(*fields)` - **功能**: 移除重复项,支持基于特定字段去重。 - **示例**: ```python unique_authors = Entry.objects.distinct('author') ``` ##### (2) `annotate(*args, **kwargs)` - **功能**: 为每个对象附加额外的信息(如计数或其他聚合值)。 - **示例**: ```python from django.db.models import Count authors_with_post_count = Author.objects.annotate(post_count=Count('posts')) ``` ##### (3) `prefetch_related(*lookups)` - **功能**: 减少 N+1 查询问题,预先获取相关联的数据。 - **示例**: ```python books_with_authors = Book.objects.prefetch_related('authors') ``` ##### (4) `select_related(*fields)` - **功能**: 对于一对一或多对一关系,提前加载关联表数据。 - **示例**: ```python entries_with_blog = Entry.objects.select_related('blog') ``` --- ### 结论 以上总结了 Django ORM QuerySet 的主要方法及其应用场景。每种方法都有其独特的用途,在开发过程中应根据实际情况灵活选用,以达到最佳性能和可维护性的平衡。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值