[转] 高效使用 django 的 queryset
1. Django
的 QuerySet
是惰性的
Django
的 QuerySet
对应于数据库的若干记录(row
),通过可选的查询来过滤。例如,下面的代码会得到数据库中名字为 Dave
的所有的人:
person_set = Person.objects.filter(first_name="Dave")
上面的代码并没有运行任何的数据库查询。你可以使用 person_set
,给它加上一些过滤条件,或者将它传给某个函数,这些操作都不会发送给数据库。这是对的,因为数据库查询是显著影响 web
应用性能的因素之一。
2. 真正从数据库获得数据
要真正从数据库获得数据,你可以遍历 QuerySet
或者使用 if QuerySet
,总之你用到数据时就会执行 sql
。为了验证这些,需要在 settings
里加入 LOGGING
,验证方式:
obj=models.Book.objects.filter(id=3)
# for i in obj:
# print(i)
# if obj:
# print("ok")
3. QuerySet
是具有 cache
的
当你遍历 QuerySet
时,所有匹配的记录会从数据库获取,然后转换成 Django
的 model
。这被称为执行(evaluation
)。这些 model
会保存在 QuerySet
内置的 cache
中,这样如果你再次遍历这个 QuerySet
,你不需要重复运行通用的查询。
obj=models.Book.objects.filter(id=3)
# for i in obj:
# print(i)
## models.Book.objects.filter(id=3).update(title="GO")
## obj_new=models.Book.objects.filter(id=3)
# for i in obj:
# print(i) #LOGGING只会打印一次
4. 验证 cache
中是否有数据
简单的使用 if
语句进行判断也会完全执行整个 QuerySet
并且把数据放入 cache
,虽然你并不需要这些数据!为了避免这个,可以用 exists()
方法来检查是否有数据:
obj = Book.objects.filter(id=4)
# exists()的检查可以避免数据放入queryset的cache。
if obj.exists():
print("hello world!")
5. 当 QuerySet
非常巨大时,cache
会成为问题
处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的 QuerySet
可能会锁住系统进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生 QuerySet cache
,可以使用 iterator()
方法来获取数据,处理完数据就将其丢弃。
objs = Book.objects.all().iterator()
# iterator()可以一次只从数据库获取少量数据,这样可以节省内存
for obj in objs:
print(obj.name)
#BUT,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了
for obj in objs:
print(obj.name)
当然,使用 iterator()
方法来防止生成 cache
,意味着遍历同一个 QuerySet
时会重复执行查询。所以使用 iterator()
的时候要当心,确保你的代码在操作一个大的 QuerySet
时没有重复执行查询。
6. 总结
QuerySet
的 cache
是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。使用 exists()
和 iterator()
方法可以优化程序对内存的使用。不过,由于它们并不会生成 QuerySet cache
,可能会造成额外的数据库查询。