В Django, какой самый эффективный способ проверить пустой набор запросов?
Я слышал предложения использовать следующее:
if qs.exists():
...
if qs.count():
...
try:
qs[0]
except IndexError:
...
Скопировано из комментария ниже: "Я ищу инструкцию типа" В MySQL и PostgreSQL count() быстрее для коротких запросов, существует() быстрее для длинных запросов и использует QuerySet [0], когда это вероятно, что вам понадобится первый элемент, и вы хотите проверить, что он существует. Однако, когда count() работает быстрее, он только немного быстрее, поэтому всегда рекомендуется использовать exists() при выборе между ними.
Ответы
Ответ 1
Похоже, что qs.count() и qs.exists() эффективно эквивалентны. Поэтому я не нашел причины использовать exists() над count(). Последний не медленнее, и его можно использовать для проверки как существования, так и длины. Возможно, что оба метода exists() и 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
Я использовал случайный запрос для каждого прохода, чтобы уменьшить риск кэширования уровня db. Тестовый код:
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
query.exists()
является наиболее эффективным способом.
Особенно в postgres count()
может быть очень дорого, иногда дороже, чем обычный запрос выбора.
exists()
запускает запрос без выбора по выбору, выбора поля или сортировки и извлекает только одну запись. Это намного быстрее, чем подсчет всего запроса с объединением таблиц и сортировкой.
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()
Еще один вопрос, который получил меня на днях, вызывает QuerySet в выражении if. Это выполняет и возвращает весь запрос!
Если переменная query_set может быть None
(аргумент unset для вашей функции), используйте:
if query_set is None:
#
not:
if query_set:
# you just hit the database
Ответ 3
Это зависит от контекста использования.
Согласно документации:
Использовать QuerySet.count()
... если вам нужен только счет, а не делать len (queryset).
Использовать QuerySet.exists()
... если вы хотите узнать, существует ли хотя бы один результат, а не запрос.
Но:
Не злоупотребляйте count() и существует()
Если вам понадобятся другие данные из QuerySet, просто оцените его.
Итак, я думаю, что QuerySet.exists()
является наиболее рекомендуемым способом, если вы просто хотите проверить пустой QuerySet. С другой стороны, если вы хотите использовать результаты позже, лучше оценить его.
Я также считаю, что ваш третий вариант является самым дорогим, потому что вам нужно получить все записи, чтобы проверить, существует ли какой-либо файл.
Ответ 4
@Решение Sam Odio было достойной отправной точкой, но есть несколько недостатков в методологии, а именно:
- Случайный IP-адрес может привести к совпадению 0 или очень мало результатов.
- Исключение будет искажать результаты, поэтому мы должны стремиться избегать обработки исключений.
Поэтому вместо того, чтобы фильтровать что-то, что могло бы совпадать, я решил исключить то, что определенно не будет соответствовать, но, надеюсь, все равно избегать кеша БД, но также обеспечивает одинаковое количество строк.
Я тестировал только локальную базу данных 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()
примерно в 9 раз медленнее, чем exists()
для этого набора данных.
[0]
также работает быстро, но ему нужна обработка исключений.
Ответ 5
Я бы предположил, что первый метод является наиболее эффективным способом (его можно легко реализовать в терминах второго метода, поэтому, возможно, они почти идентичны). Последнее требует фактически получить весь объект из базы данных, поэтому он почти наверняка самый дорогой.
Но, как и все эти вопросы, единственный способ узнать вашу конкретную базу данных, схему и набор данных - проверить ее самостоятельно.