[英]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):
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
( None
设置函数的参数),则使用:
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/passqs.exists()
: 144.46 使用 c/passqs.count()
144.33 usec/passqs[0]
: 324.98 使用 c/pass在 MySQL 上,57 行:
qs
: 1.07 usec/passqs.exists()
: 1.21 usec/passqs.count()
: 1.16 usec/passqs[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 的解决方案是一个不错的起点,但该方法存在一些缺陷,即:
因此,我没有过滤可能匹配的内容,而是决定排除肯定不匹配的内容,希望仍然避免使用 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.