Как эффективно выполнять итерацию по многим объектам foreignkey с помощью Django ORM?
Допустим, у меня есть model
, например:
class User(AbstractUser):
...
seller = models.ForeignKey(Seller, related_name="user", on_delete=models.SET_NULL, null=True)
...
Я пытаюсь получить seller
адрес электронной почты, используя этот код:
from app.models import User
from django_print_sql import print_sql
with print_sql(count_only=False):
users = User.objects.filter(is_active=True, seller_id__isnull=False).select_related().only('seller__email')
for u in users.iterator():
email = u.seller.email
send_email(email)
В этом случае я вижу SQL-запросы типа:
SELECT `user`.`id`,
`user`.`seller_id`
FROM `user`
WHERE (`user`.`is_active`
AND `user`.`seller_id` IS NOT NULL)
...
SELECT `seller`.`id`,
...
`seller`.`email`,
...
FROM `seller`
WHERE `seller`.`id` = 1
...
Проблема в том, что Django ORM обращается к БД на каждой итерации (Select seller... where seller.id = ...
). Так что это будет слишком много запросов (== соединений с БД), если у нас много продавцов.
Другим способом можно заменить only
на values
:
from app.models import User
from django_print_sql import print_sql
with print_sql(count_only=False):
users = User.objects.filter(is_active=True, seller_id__isnull=False).select_related().values('seller__email')
for u in users.iterator():
email = u['seller__email']
send_email(email)
И я могу видеть SQL запрос типа:
SELECT `seller`.`email`
FROM `user`
INNER JOIN `seller` ON (`user`.`seller_id` = `seller`.`id`)
WHERE (`user`.`is_active`
AND `user`.`seller_id` IS NOT NULL)
Это немного лучше, и мы можем получить электронные письма одним запросом к БД, но iterator
здесь бесполезно, потому что мы загружаем все электронные письма один раз в объект dict
и все данные существуют в памяти.
Вопрос такой
.
Можно ли сделать итерацию по кускам данных (скажем, по 500 писем/одному запросу) без создания ручных подциклов, ограничивающих User.objects.filter
количество запросов? Или, другими словами, какой итератор наиболее эффективен в данном случае?
Похоже, что select_related
работает для этого случая:
from app.models import User
from django_print_sql import print_sql
with print_sql(count_only=False):
users = User.objects.filter(is_active=True, seller_id__isnull=False).select_related('seller').only('seller__email').exclude(seller__email__exact='')
for u in users.iterator():
send_email(u.seller.email)
SELECT `user`.`id`,
`user`.`owner_id`,
`seller`.`id`,
`seller`.`email`
FROM `user`
INNER JOIN `seller` ON (`user`.`seller_id` = `seller`.`id`)
WHERE (`user`.`is_active`
AND `user`.`seller_id` IS NOT NULL
AND NOT (`seller`.`email` =
AND `seller`.`email` IS NOT NULL))
В вашем случае вы не указываете параметров для select_related
, я предлагаю вам поместить seller
поле в select_related
следующим образом:
from app.models import User
from django_print_sql import print_sql
with print_sql(count_only=False):
users = User.objects.select_related('seller').filter(is_active=True, seller_id__isnull=False).only('seller__email')
for u in users.iterator():
email = u.seller.email
send_email(email)
помещая select_related
перед objects
менеджером. Делая это, django получает записи модели продавца как поле для каждого итерируемого пользователя и не будет обращаться к базе данных в каждой итерации.