Sql 在Django中,检查空查询集最有效的方法是什么?
我听到了使用以下内容的建议:Sql 在Django中,检查空查询集最有效的方法是什么?,sql,django,performance,django-queryset,Sql,Django,Performance,Django Queryset,我听到了使用以下内容的建议: if qs.exists(): ... if qs.count(): ... try: qs[0] except IndexError: ... 从下面的注释中复制:“我正在寻找一个类似于”的语句,在MySQL和PostgreSQL count()中,短查询速度更快,exists()中,长查询速度更快,当您可能需要第一个元素并且希望检查它是否存在时,请使用QuerySet[0]。但是,当count()更快时,它只会稍微快一点,因
if qs.exists():
...
if qs.count():
...
try:
qs[0]
except IndexError:
...
从下面的注释中复制:“我正在寻找一个类似于”的语句,在MySQL和PostgreSQL count()中,短查询速度更快,exists()中,长查询速度更快,当您可能需要第一个元素并且希望检查它是否存在时,请使用QuerySet[0]。但是,当count()更快时,它只会稍微快一点,因此建议在两者之间进行选择时始终使用exists()。我认为第一种方法是最有效的方法(您可以很容易地用第二种方法实现它,因此它们可能几乎相同).最后一个需要从数据库中实际获取整个对象,因此几乎可以肯定这是最昂贵的
但是,像所有这些问题一样,了解特定数据库、模式和数据集的唯一方法是自己测试它。这取决于使用上下文 根据: 使用QuerySet.count() …如果您只需要计数,而不是执行len(queryset) 使用QuerySet.exists() …如果您只想确定是否至少存在一个结果,而不是查询集 但是: 不要过度使用count()和exists() 如果您需要来自QuerySet的其他数据,只需对其进行评估 因此,如果您只想检查一个空的QuerySet,我认为
QuerySet.exists()
是最推荐的方法。另一方面,如果您想稍后使用结果,最好对其进行评估
我还认为您的第三个选项是最昂贵的,因为您需要检索所有记录以检查是否存在任何记录。exists()通常比count()快,但并不总是如此(请参见下面的测试)。count()可用于检查记录的存在性和长度
如果您确实需要对象,请仅使用qs[0]
。如果您只是测试对象是否存在,则速度会明显减慢
在Amazon SimpleDB上,400000行:
- 裸
:325.00 usec/passqs
:144.46 usec/passqs.exists()
144.33 usec/passqs.count()
:324.98 usec/passqs[0]
- 裸
:1.07 usec/通行证qs
:1.21 usec/passqs.exists()
:1.16 usec/passqs.count()
:1.27 usec/passqs[0]
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)
query.exists()
是最有效的方法
尤其是在postgres上,count()
可能非常昂贵,有时比普通的select查询更昂贵
exists()
运行一个查询,没有选择、字段选择或排序,只获取一条记录。这比使用表联接和排序计算整个查询要快得多
qs[0]
仍将包括与选择相关的字段选择和排序;因此成本会更高
Django源代码如下(Django/db/models/sql/query.py RawQuery.has_results):
前几天我遇到的另一个问题是在if语句中调用QuerySet。它执行并返回整个查询
如果变量查询集可能是None
(未设置函数参数),则使用:
if query_set is None:
#
不是:
这是一个不错的起点,但该方法存在一些缺陷,即:
>>> 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]
也很快,但它需要异常处理。我也遇到了这个问题。是的exists()
在大多数情况下速度更快,但这在很大程度上取决于您尝试执行的查询集的类型。例如,对于以下简单查询:
my\u objects=MyObject.objets.all()
您会使用my\u objects.exists()
。但是如果您要执行以下查询:MyObject.objects.filter(some\u attr='anywhere')。exclude(something='what')。distinct('key')。value()
可能需要测试哪一个更适合(exists()
,count()
,len(我的对象)
)。请记住,DB引擎将执行查询,要获得良好的性能结果,这在很大程度上取决于数据结构和查询的形成方式。您可以做的一件事是,审核查询并根据DB引擎对其进行测试,然后将结果与django进行比较。您会惊讶于django有时有多么幼稚,请尝试QueryCountMiddleware
查看执行的所有查询,您将看到我所说的内容。我非常确定最后一个选项只获得一条记录,而不是所有记录。此外,从文档中还不清楚存在的速度是否快于计数。如果是,我想知道速度增量,以及它是否变化很大y基于查询集的长度等条件。你是对的-最后一个选项将被转换为类似于从bar LIMIT 1选择foo
。这里我们要说的是-所有这些方法都是ORM API调用并转换为SQL查询。这就是为什么文档不能说存在
比count
。或者它的速度快了多少倍。这取决于这些数据库的实现、配置和相关数据集。我认为clauless是正确的-您需要自己测试它才能获得
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