Django: есть ли разница в производительности между .filter (key__in = set) и .filter (key__in = list) - PullRequest
0 голосов
/ 30 сентября 2019

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

some_set = set(some_list)
some_qs = SomeModel.objects.filter(some_key__in=some_set)

vs

some_qs = SomeModel.objects.filter(some_key__in=some_list)

Я знаю, что если вы хотите искать элементы из списка a в другом списке b, обычно стоит преобразовать список b в набор, поскольку x in list представляет собой O (n) и x in set представляет собой O (1). Но я не знаю, как Django реализует фильтр запросов __in, поэтому я не уверен, какой вариант лучше в этом контексте.

Ответы [ 2 ]

1 голос
/ 30 сентября 2019

В случае сомнений вы можете напечатать my_query.explain(), чтобы увидеть, есть ли разница между этими двумя вариантами. В вашем случае это очень маловероятно, поскольку x in set действительно происходит на стороне SQL, а не на стороне python, и django преобразует вашу итерацию в строку перед отправкой в ​​sql.

0 голосов
/ 30 сентября 2019

Это может зависеть от системы БД, поэтому прежде всего я должен отметить, что я использую PostgreSQL.

Следуя предложению blue_note, я посмотрел план выполнения запроса с .explain(), и он был идентичен(за исключением порядка элементов) для списка, каждый элемент которого повторяется 4 раза, и для набора с такими же элементами. Это означает, что Django создает какой-то список для внутренней конвертации. Затем я использовал timeit, чтобы увидеть, какой из двух подходов быстрее, и оказалось, что сначала преобразование списка в набор происходит быстрее. Вот мой код:

from timeit import timeit

qs = SomeModel.objects.all()

l = [str(x.id) for x in qs]

l.extend(l)
l.extend(l)

def t_l():
  return SomeModel.objects.filter(id__in=l)

def t_s():
  return SomeModel.objects.filter(id__in=set(l))

>>> SomeModel.objects.filter(id__in=l).explain()
"Bitmap Heap Scan on authentication_permissionsvatnumber  (cost=9.26..19.48 rows=8 width=86)\n  Recheck Cond: (id = ANY ('{824ea540-6fac-4929-b5cc-306890356ca1,349d3d2d-f8f8-45b7-9e80-538189b455e4,7d1bc4aa-9442-4fbc-a419-67f56483cd8c,8dd98c0e-e792-4b5c-80f6-c08de202b888,0dfddb54-19ae-4204-82a3-183ec01efb83,93c1fb80-b464-4651-b8c3-7928eaf56790,7d7aa210-1979-4f90-8680-a3dc7e685a71,6db11e75-6fae-421e-bda5-be22b1b2e82a}'::uuid[]))\n  ->  Bitmap Index Scan on authentication_permissionsvatnumber_pkey  (cost=0.00..9.26 rows=8 width=0)\n        Index Cond: (id = ANY ('{824ea540-6fac-4929-b5cc-306890356ca1,349d3d2d-f8f8-45b7-9e80-538189b455e4,7d1bc4aa-9442-4fbc-a419-67f56483cd8c,8dd98c0e-e792-4b5c-80f6-c08de202b888,0dfddb54-19ae-4204-82a3-183ec01efb83,93c1fb80-b464-4651-b8c3-7928eaf56790,7d7aa210-1979-4f90-8680-a3dc7e685a71,6db11e75-6fae-421e-bda5-be22b1b2e82a}'::uuid[]))"

>>> SomeModel.objects.filter(id__in=set(l)).explain()
"Bitmap Heap Scan on authentication_permissionsvatnumber  (cost=9.26..19.48 rows=8 width=86)\n  Recheck Cond: (id = ANY ('{0dfddb54-19ae-4204-82a3-183ec01efb83,8dd98c0e-e792-4b5c-80f6-c08de202b888,93c1fb80-b464-4651-b8c3-7928eaf56790,7d7aa210-1979-4f90-8680-a3dc7e685a71,6db11e75-6fae-421e-bda5-be22b1b2e82a,349d3d2d-f8f8-45b7-9e80-538189b455e4,7d1bc4aa-9442-4fbc-a419-67f56483cd8c,824ea540-6fac-4929-b5cc-306890356ca1}'::uuid[]))\n  ->  Bitmap Index Scan on authentication_permissionsvatnumber_pkey  (cost=0.00..9.26 rows=8 width=0)\n        Index Cond: (id = ANY ('{0dfddb54-19ae-4204-82a3-183ec01efb83,8dd98c0e-e792-4b5c-80f6-c08de202b888,93c1fb80-b464-4651-b8c3-7928eaf56790,7d7aa210-1979-4f90-8680-a3dc7e685a71,6db11e75-6fae-421e-bda5-be22b1b2e82a,349d3d2d-f8f8-45b7-9e80-538189b455e4,7d1bc4aa-9442-4fbc-a419-67f56483cd8c,824ea540-6fac-4929-b5cc-306890356ca1}'::uuid[]))"


>>> timeit(t_l, number=10000)
0.9831858319999967

>>> timeit(t_s, number=10000)
0.7851520270000023

Но я также хотел посмотреть, что происходит, когда список и набор уже имеют одинаковое количество уникальных элементов:

>>> timeit(t_l, number=10000)
0.8463515899999976

>>> timeit(t_s, number=10000)
0.7903294700000174

Как выясняется, преобразованиесписок для набора первым все еще быстрее, хотя и с гораздо меньшим запасом. Если бы я предположил, мне кажется, что Django всегда запускает какой-то список для внутренней конвертации перед отправкой запроса в БД.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...