繁体   English   中英

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

[英]In Django, what is the most efficient way to check for an empty query set?

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

if qs.exists():
    ...

if qs.count():
    ...

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

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

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 可能是NoneNone设置函数的参数),则使用:

if query_set is None:
    # 

不是:

if query_set:
   # you just hit the database

exists() 通常比 count() 快,但并非总是如此(请参阅下面的测试)。 count() 可用于检查是否存在和长度。

仅当您确实需要该对象时才使用qs[0] 如果您只是在测试是否存在,它会明显变慢。

在 Amazon SimpleDB 上,400,000 行:

  • qs : 325.00 usec/pass
  • qs.exists() : 144.46 使用 c/pass
  • qs.count() 144.33 usec/pass
  • qs[0] : 324.98 使用 c/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)

这取决于使用上下文。

根据文档

使用 QuerySet.count()

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

使用 QuerySet.exists()

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

但:

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

如果您需要来自 QuerySet 的其他数据,只需评估它。

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

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

@Sam Odio 的解决方案是一个不错的起点,但该方法存在一些缺陷,即:

  1. 随机 IP 地址可能最终匹配 0 个或很少的结果
  2. 异常会扭曲结果,所以我们应该尽量避免处理异常

因此,我没有过滤可能匹配的内容,而是决定排除肯定不匹配的内容,希望仍然避免使用 DB 缓存,但也确保相同的行数。

我只针对本地 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]也很快,但需要异常处理。

我想第一种方法是最有效的方法(你可以很容易地用第二种方法实现它,所以也许它们几乎相同)。 最后一个实际上需要从数据库中获取整个对象,因此几乎可以肯定它是最昂贵的。

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

我也遇到了这个麻烦。 Yes 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) )。 请记住,数据库引擎是执行查询的引擎,要获得良好的性能结果,很大程度上取决于数据结构和查询的形成方式。 您可以做的一件事是,审核查询并针对数据库引擎自行测试它们并比较您的结果,您会惊讶于 django 有时是多么天真,尝试使用QueryCountMiddleware查看执行的所有查询,您将看到我所做的我在谈论。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM