python

django查询集使用建议

文章暂存

systemime
2020-08-12
6 min

摘要.

原文链接:https://blog.etianen.com/blog/2013/06/08/django-querysets/

# Django查询集很懒

Django中的queryset代表数据库中的许多行,可以选择由查询过滤。例如,以下代码表示数据库中所有名字为“ Dave”的人:

person_set = Person.objects.filter(first_name="Dave")


上面的代码不运行任何数据库查询。您可以采用person_set并应用其他过滤器,也可以将其传递给函数,而不会将任何内容发送到数据库。这很好,因为查询数据库是大大降低Web应用程序速度的因素之一。
要从数据库中获取数据,您需要遍历查询集:

for person in person_set:
    print(person.last_name)

# Django查询集具有一个缓存

从查询集开始迭代的那一刻起,将从数据库中获取与查询集匹配的所有行,并将其转换为Django模型。这称为_评估_。然后,这些模型由查询集的内置缓存存储,因此,如果再次遍历查询集,则不会最终两次运行相同的查询。

例如,以下代码将仅执行一个数据库查询:

pet_set = Pet.objects.filter(species="Dog")
# The query is executed and cached.
for pet in pet_set:
    print(pet.first_name)
    
# The cache is used for subsequent iteration.
for pet in pet_set:
    print(pet.last_name)

# if 语句触发查询集评估

关于查询集缓存的最有用的事情是,它允许您有效地测试查询集是否包含行,然后仅在找到至少一行的情况下对其进行迭代:

restaurant_set = Restaurant.objects.filter(cuisine="Indian")
# The `if` statement evaluates the queryset.
if restaurant_set:
    # The cache is used for subsequent iteration.
    for restaurant in restaurant_set:
        print(restaurant.name)

# 如果您不需要所有结果,则查询集缓存是一个问题

有时,您只想查看是否存在至少一个结果,而不是遍历结果。在那种情况下,if即使您从未计划使用这些结果,仅在queryset上使用一条语句仍将完全评估该queryset并填充其缓存!

city_set = City.objects.filter(name="Cambridge")
# The `if` statement evaluates the queryset.
if city_set:
    # We don't need the results of the queryset here, but the
    # ORM still fetched all the rows!
    print("At least one city called Cambridge still stands!")

为避免这种情况,请使用该exists()方法检查是否找到至少一个匹配的行:

tree_set = Tree.objects.filter(type="deciduous")
# The `exists()` check avoids populating the queryset cache.
if tree_set.exists():
    # No rows were fetched from the database, so we save on
    # bandwidth and memory.
    print("There are still hardwood trees in the world!")

# 如果您的查询集很大,则查询集缓存会出现问题

如果您要处理数千行数据,那么一次将它们全部提取到内存中可能会非常浪费。更糟糕的是,庞大的查询集可以锁定服务器进程,从而导致整个Web应用程序无法运行。
为了避免填充查询集缓存,但仍要遍历所有结果,请使用该iterator()方法以块的形式获取数据,并在处理完旧行后丢弃它们。

star_set = Star.objects.all()
# The `iterator()` method ensures only a few rows are fetched from
# the database at a time, saving memory.
for star in star_set.iterator():
    print(star.name)


当然,使用该iterator()方法避免填充查询集缓存意味着再次迭代相同的查询集将执行另一个查询。因此iterator(),请谨慎使用,并确保您的代码井井有条,以避免重复评估同一庞大的查询集。

# if 如果您的查询集很大,则语句是一个问题

如前所示,查询集缓存非常适合将if语句与for语句组合在一起,从而允许在查询集上进行条件迭代。但是,对于庞大的查询集,则不能选择填充查询集缓存。

最简单的解决方案是与结合exists()使用iterator(),避免以运行两个数据库查询为代价来填充查询集缓存。

molecule_set = Molecule.objects.all()
# One database query to test if any rows exist.
if molecule_set.exists():
    # Another database query to start fetching the rows in batches.
    for molecule in molecule_set.iterator():
        print(molecule.velocity)


一个更复杂的解决方案是在确定是否继续迭代之前,利用Python的高级迭代方法来窥视第一个项目iterator()

atom_set = Atom.objects.all()
# One database query to start fetching the rows in batches.
atom_iterator = atom_set.iterator()
# Peek at the first item in the iterator.
try:
    first_atom = next(atom_iterator)
except StopIteration:
    # No rows were found, so do nothing.
    pass
else:
    # At least one row was found, so iterate over
    # all the rows, including the first one.
    from itertools import chain
    for atom in chain([first_atom], atom_iterator):
        print(atom.mass)

# 提防天真的优化

存在查询集缓存是为了减少应用程序进行的数据库查询的数量,在正常使用情况下,将确保仅在必要时才查询数据库。

使用exists()iterator()方法,可以优化应用程序的内存使用率。但是,由于它们不填充查询集缓存,因此可能导致额外的数据库查询。

因此,请仔细编写代码,如果情况开始放慢,请查看代码中的瓶颈,并查看一些查询集优化是否可以帮助解决问题。

上次编辑于: 2021/5/20 下午3:26:49