在Django中,检查空查询集的最有效方法是什么?

时间:2011-07-29 05:18:26

标签: sql django performance django-queryset

我听说过使用以下内容的建议:

if qs.exists():
    ...

if qs.count():
    ...

try:
    qs[0]
except IndexError:
    ...

从下面的评论复制:“我正在寻找一个声明,例如”在MySQL中,PostgreSQL count()对于短查询更快,exists()对于长查询更快,并且在可能的情况下使用QuerySet [0]你将需要第一个元素,并且你想检查它是否存在。但是,当count()更快时,它只会稍微快一些,因此建议在两者之间选择时始终使用exists()。“

6 个答案:

答案 0 :(得分:15)

query.exists()是最有效的方式。

特别是对于postgres count()可能非常昂贵,有时比普通的选择查询更贵。

exists()运行没有select_related,字段选择或排序的查询,只获取单个记录。这比使用表连接和排序计算整个查询要快得多。

qs[0]仍会包含select_related,字段选择和排序;所以它会更贵。

Django源代码在这里(django / db / models / sql / query.py RawQuery.has_results):

https://github.com/django/django/blob/60e52a047e55bc4cd5a93a8bd4d07baed27e9a22/django/db/models/sql/query.py#L499

def has_results(self, using):
    q = self.clone()
    if not q.distinct:
        q.clear_select_clause()
    q.clear_ordering(True)
    q.set_limits(high=1)
    compiler = q.get_compiler(using=using)
    return compiler.has_results()

前几天弄到我的另一个问题是在if语句中调用QuerySet。执行并返回整个查询!

如果变量query_set可能是None(函数的未设置参数),那么使用:

if query_set is None:
    # 

不是:

if query_set:
   # you just hit the database

答案 1 :(得分:14)

看起来像 qs.count()和qs.exists()实际上是等效的。因此我没有发现使用exists()而不是count()的原因。后者并不慢,它可以用来检查存在和长度。存在()和count()可能会在MySQL中对同一个查询进行求值。

如果您确实需要该对象,请仅使用qs[0]。如果你只是测试存在,它会慢得多。

在Amazon SimpleDB上,400,000行:

  • qs:325.00 usec / pass
  • qs.exists():144.46 usec / pass
  • qs.count() 144.33 usec / pass
  • qs[0]:324.98 usec / pass

在MySQL上,57行:

  • qs:1.07 usec / pass
  • qs.exists():1.21 usec / pass
  • qs.count():1.16 usec / pass
  • qs[0]:1.27 usec / pass

我为每次传递使用了随机查询,以降低数据库级缓存的风险。测试代码:

import timeit

base = """
import random
from plum.bacon.models import Session
ip_addr = str(random.randint(0,256))+'.'+str(random.randint(0,256))+'.'+str(random.randint(0,256))+'.'+str(random.randint(0,256))
try:
    session = Session.objects.filter(ip=ip_addr)%s
    if session:
        pass
except:
    pass
"""

query_variatons = [
    base % "",
    base  % ".exists()",
    base  % ".count()",
    base  % "[0]"
    ]

for s in query_variatons:
    t = timeit.Timer(stmt=s)
    print "%.2f usec/pass" % (1000000 * t.timeit(number=100)/100000)

答案 2 :(得分:7)

这取决于使用环境。

根据documentation

  

使用QuerySet.count()

     

...如果你只想要计数,而不是做len(queryset)。

     

使用QuerySet.exists()

     

...如果您只想知道是否存在至少一个结果,而不是查询。

     

可是:

     

不要过度使用count()和exists()

     

如果您需要QuerySet中的其他数据,只需对其进行评估。

所以,我认为如果你只想检查一个空的QuerySet,QuerySet.exists()是最推荐的方法。另一方面,如果您想稍后使用结果,最好对其进行评估。

我还认为您的第三个选项是最昂贵的,因为您需要检索所有记录以检查是否存在。

答案 3 :(得分:4)

@Sam Odio's solution是一个不错的起点,但方法中存在一些缺陷,即:

  1. 随机IP地址可能最终匹配0或非常少的结果
  2. 异常会使结果出现偏差,因此我们的目标应该是避免处理异常
  3. 因此,我没有过滤掉可能匹配的东西,而是决定排除一些绝对不匹配的东西,希望仍能避免数据库缓存,同时也要确保相同的行数。

    我只使用数据集:

    对本地MySQL数据库进行了测试
    >>> Session.objects.all().count()
    40219
    

    时间码:

    import timeit
    base = """
    import random
    import string
    from django.contrib.sessions.models import Session
    never_match = ''.join(random.choice(string.ascii_uppercase) for _ in range(10))
    sessions = Session.objects.exclude(session_key=never_match){}
    if sessions:
        pass
    """
    s = base.format('count')
    
    query_variations = [
        "",
        ".exists()",
        ".count()",
        "[0]",
    ]
    
    for variation in query_variations:
        t = timeit.Timer(stmt=base.format(variation))
        print "{} => {:02f} usec/pass".format(variation.ljust(10), 1000000 * t.timeit(number=100)/100000)
    

    输出:

               => 1390.177710 usec/pass
    .exists()  => 2.479579 usec/pass
    .count()   => 22.426991 usec/pass
    [0]        => 2.437079 usec/pass
    

    因此,您可以看到count()对于此数据集的速度大约是exists()的9倍。

    [0]也很快,但需要处理异常。

答案 4 :(得分:1)

我认为第一种方法是最有效的方法(你可以根据第二种方法轻松实现它,所以它们可能几乎相同)。最后一个实际上需要从数据库中获取一个完整的对象,所以它几乎肯定是最昂贵的。

但是,就像所有这些问题一样,了解特定数据库,架构和数据集的唯一方法就是自己测试它。

答案 5 :(得分:0)

我也遇到了麻烦。是的,exists()在大多数情况下速度更快,但这在很大程度上取决于您要执行的查询集的类型。例如,对于一个简单的查询,例如: my_objects = MyObject.objets.all(),您将使用my_objects.exists()。但是,如果要执行类似MyObject.objects.filter(some_attr='anything').exclude(something='what').distinct('key').values()的查询,则可能需要测试哪个更合适(exists()count()len(my_objects))。请记住,DB引擎是执行查询的引擎,要获得良好的性能结果,它很大程度上取决于数据结构和查询的形成方式。您可以做的一件事是,审核查询并针对数据库引擎自己对它们进行测试,然后比较您的结果,有时django的天真性会让您感到惊讶,请尝试QueryCountMiddleware查看所有执行的查询,然后您会明白我在说什么。