Django - повторяющиеся / неэффективные запросы - PullRequest
3 голосов
/ 18 февраля 2009

Хорошо, у меня есть вид Django, например:

@render_to('home/main.html')
def login(request):
    # also tried Client.objects.select_related().all()
    clients = Client.objects.all()
    return {'clients':clients}

И у меня есть шаблон, main.html, например:

<ul>
{% for client in clients %}
<li>{{ client.full_name }}</li>
    <ul>
    {% for pet in client.pets.all %}
        <li>{{ pet.full_name }}</li>
    {% endfor %}
    </ul>
{% endfor %}
</ul>

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

SELECT `home_client`.`id`, ... FROM `home_client`;
SELECT `home_pet`.`id`, ... FROM `home_pet` WHERE `home_pet`.`client_id` = 1;
SELECT `home_client`.`id`, ... FROM `home_client` WHERE `home_client`.`id` = 1;
SELECT `home_client`.`id`, ... FROM `home_client` WHERE `home_client`.`id` = 1;
SELECT `home_pet`.`id`, ... FROM `home_pet` WHERE `home_pet`.`client_id` = 2;
SELECT `home_client`.`id`, ... FROM `home_client` WHERE `home_client`.`id` = 2; 

Мой вопрос: почему все эти запросы выполняются? Разве это не один запрос на получение всех клиентов и запрос на каждого клиента для получения всех домашних животных от каждого клиента? У меня в таблице home_client 2 клиента, поэтому всего должно быть 3 запроса. Больше всего беспокоит то, что запросы 3 и 4 идентичны на 100%. Я не хочу «преждевременно оптимизировать» или что-то еще, но я хочу убедиться, что Django не является дико неэффективным. Любая помощь в этом будет оценена. Спасибо.

Ответы [ 3 ]

7 голосов
/ 18 февраля 2009

Джанго использует кеш. СУБД использует кеш. Не преждевременно оптимизируйте запросы.

Вы можете играть с массовыми запросами в функции просмотра, а не по одному в вашем шаблоне.

@render_to('home/main.html')
def login(request):
    # Query all clients 
    clients = Client.objects.all()
    # Assemble an in-memory table of pets
    pets = collections.defaultdict(list)
    for p in Pet.objects.all():
        pets[pet.client].append(p)
    # Create clients and pets tuples
    clientsPetTuples = [ (c,pets[c]) for c in clients ]
    return {'clientPets': clientsPetTuples}

Однако у вас нет никаких доказательств того, что ваш шаблон является самой медленной частью вашего приложения.

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

Не беспокойтесь о SQL, пока у вас нет доказательств.

4 голосов
/ 18 февраля 2009

попробуйте использовать Client.objects.all (). Select_related ()

Это также автоматически кеширует связанные модели в одном запросе к базе данных.

3 голосов
/ 18 февраля 2009

Есть ли у Клиента 1 2 питомца, а у Клиента 2 1 питомец?

Если это так, это будет означать, что Pet.full_name или что-то еще, что вы делаете в цикле отображения питомца, пытается получить доступ к данным соответствующего клиента. ORM в Django не использует идентификационную карту , поэтому для доступа к внешнему ключу Клиента из любого из ваших объектов Pet потребуется повторное нажатие на базу данных для получения этого Клиента.

P.S. select_related не окажет никакого влияния на данные, которые вы используете в этом сценарии, поскольку оно только следует отношениям внешнего ключа, но отношение «домашнее животное-клиент» много-к-одному.

Обновление: если вы хотите избежать необходимости изменять логику в Pet.full_name или выполнять вместо этого указанную логику в шаблоне для этого случая, вы можете изменить способ получения дескриптора для каждого Домашние животные Клиента для того, чтобы предварительно заполнить кэш ForeignKey для каждого Домашнего животного с его Клиентом:

class Client(models.Model):
    # ...
    def get_pets(self):
        for pet in self.pets.all():
            setattr(pet, '_client_cache', self)
            yield pet

... где 'client' часть '_client_cache' - это любое имя атрибута, которое используется в классе Pet для ForeignKey для клиента Pet. Это использует преимущества того, как Django реализует доступ к объектам, связанным с ForeignKey, используя свой класс SingleRelatedObjectDescriptor, который ищет этот атрибут кэша перед запросом к базе данных.

Результирующее использование шаблона:

{% for pet in client.get_pets %}
...
{% endfor %}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...