В Django, какой самый эффективный способ проверить наличие пустого набора запросов?
Я слышал предложения использовать следующее:
if qs.exists():
...
if qs.count():
...
try:
qs[0]
except IndexError:
...
Скопировано из комментария ниже: "Я ищу утверждение типа" В MySQL и PostgreSQL count() быстрее для коротких запросов, существует () быстрее для длинных запросов и использует QuerySet[0], когда вероятно, что вы Вам понадобится первый элемент, и вы хотите проверить, существует ли он. Тем не менее, когда count () быстрее, он лишь незначительно быстрее, поэтому рекомендуется всегда использовать exist () при выборе между ними."
5 ответов
Похоже, qs.count() и qs.exists() фактически эквивалентны. Поэтому я не обнаружил причину использовать существовать () над count(). Последнее не медленнее, и его можно использовать для проверки как существования, так и длины. Вполне возможно, что оба метода available () и count () соответствуют одному и тому же запросу в MySQL.
Использовать только qs[0]
если вам действительно нужен объект. Это значительно медленнее, если вы просто проверяете существование.
На Amazon SimpleDB 400 000 строк:
- голый
qs
: 325,00 чел. / Пасс qs.exists()
: 144,46 чел. / Пассqs.count()
144,33 чел / минqs[0]
: 324,98 юз / пасс
На MySQL 57 строк:
- голый
qs
: 1.07 usec / pass qs.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)
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()
Еще один недочет, который получил меня на днях, вызывает QuerySet в операторе if. Это выполняет и возвращает весь запрос!
Если переменная query_set может быть None
(сбросьте аргумент для вашей функции), затем используйте:
if query_set is None:
#
не:
if query_set:
# you just hit the database
Это зависит от контекста использования.
Согласно документации:
Используйте QuerySet.count()
... если вам нужен только подсчет, а не выполнение len(queryset).
Используйте QuerySet.exists ()
... если вы хотите выяснить, существует ли хотя бы один результат, а не запросить его.
Но:
Не злоупотребляйте count() и существует ()
Если вам понадобятся другие данные из QuerySet, просто оцените их.
Итак, я думаю, что QuerySet.exists()
это наиболее рекомендуемый способ, если вы просто хотите проверить наличие пустого QuerySet. С другой стороны, если вы хотите использовать результаты позже, лучше оценить их.
Я также думаю, что ваш третий вариант самый дорогой, потому что вам нужно получить все записи, чтобы проверить, существуют ли они.
Решение @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]
также быстро, но требует обработки исключений.
Я тоже был в этой беде. да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
чтобы увидеть все выполненные запросы, и вы поймете, о чем я говорю.
Я полагаю, что первый метод является наиболее эффективным (вы могли бы легко реализовать его в терминах второго метода, поэтому, возможно, они почти идентичны). Последний требует фактического получения целого объекта из базы данных, поэтому он почти наверняка самый дорогой.
Но, как и все эти вопросы, единственный способ узнать вашу конкретную базу данных, схему и набор данных - это проверить ее самостоятельно.