В 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 / 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)

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

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 было хорошей отправной точкой, но в методологии есть несколько недостатков, а именно:

  1. Случайный IP-адрес может в конечном итоге совпадать с 0 или очень мало результатов
  2. Исключение может исказить результаты, поэтому мы должны стремиться избегать обработки исключений

Поэтому вместо того, чтобы фильтровать что-то, что могло бы соответствовать, я решил исключить что-то, что определенно не будет совпадать, надеюсь, по-прежнему избегая кеша БД, но также обеспечивая такое же количество строк.

Я проверил только локальную базу данных 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 чтобы увидеть все выполненные запросы, и вы поймете, о чем я говорю.

Я полагаю, что первый метод является наиболее эффективным (вы могли бы легко реализовать его в терминах второго метода, поэтому, возможно, они почти идентичны). Последний требует фактического получения целого объекта из базы данных, поэтому он почти наверняка самый дорогой.

Но, как и все эти вопросы, единственный способ узнать вашу конкретную базу данных, схему и набор данных - это проверить ее самостоятельно.

Другие вопросы по тегам