首页 > 解决方案 > 减少 Django 播客应用程序的数据库点击次数

问题描述

我正在构建一个可以对剧集进行分类的播客目录。为播客生成页面的代码、剧集列表以及每个剧集中的类别列表。

播客可以有“体育”和“新闻”类别。一集可能有“体育”,而另一集可能有“体育”和“新闻”作为类别。

问题:我在循环中循环攻击数据库。我知道有一种更有效的方法。

以下是模型的连接方式:

class Podcast(models.Model):
    ...

class Category(models.Model):
    podcast = models.ForeignKey(Podcast, on_delete=models.CASCADE)
    ...

class Episode(models.Model):
    podcast = models.ForeignKey(Podcast, on_delete=models.CASCADE)
    categories = models.ManyToManyField(Category, blank=True)
    ...

然后,当我加载播客页面并想要在其中放置类别和剧集列表时:

class Podcast(models.Model):
    ...

    @property
    def get_categories(self):
        return self.category_set.all()

    @property
    def episodes_by_category(self):
        episodes_by_category = []
        for category in self.get_categories:
            episodes = self.episode_set.all().filter(categories=category)
            episodes_by_category.append({
                'category': category,
                'episodes': episodes,
                'count': len(episodes),
            })

        sorted_episodes_by_category = sorted(episodes_by_category, key = lambda i: i['count'],reverse=True)
        return sorted_episodes_by_category

然后,当我加载一个剧集页面时,我会显示共享相同类别的其他剧集:

class Episode(models.Model):
    podcast = models.ForeignKey(Podcast, on_delete=models.CASCADE)
    categories = models.ManyToManyField(Category, blank=True)
    ...

    @property
    def get_categories(self):
        return self.categories.all().prefetch_related('episode_set').select_related('podcast')

    @property
    def related_episodes_by_category(self):
        episodes_by_category = []
        for category in self.get_categories:
            episodes = category.episode_set.all().select_related('podcast')
            episodes_by_category.append({
                'category': category,
                'episodes': episodes,
                'count': len(episodes),
            })
        sorted_episodes_by_category = sorted(episodes_by_category, key = lambda i: i['count'],reverse=True)
        return sorted_episodes_by_category

以下是我在播客页面上的模板中使用它的方法:

{% for episode_batch in podcast.episodes_by_category %}
    <div class="sidebar-object mb-2 pb-2" id="categories">
        <div class="section-title section-title--style-1 mb-0">
            <h3 class="section-title-inner heading-sm strong-600 text-uppercase">
                Top <a href="{{episode_batch.category.get_absolute_url_cached|check_custom_domain:request}}" class="link">{{episode_batch.category.name|title}}</a> Episodes
            </h3>
        </div>

        <ul class="categories list-border--dotted">
            {% for episode in episode_batch.episodes %}
                {% if forloop.counter < 3 %}
                    <li>
                        <a href="{{episode.get_absolute_url_cached|check_custom_domain:request}}">{{episode.name}}</a>
                    </li>
                {% endif %}
            {% endfor %}
        </ul>
    </div>
{% endfor %}

以下是我如何在剧集页面的模板中使用它:

{% for episode_batch in episode.related_episodes_by_category %}
    {% if episode_batch.category in episode_categories %}

        <div class="sidebar-object mb-2 pb-2" id="categories">
            <div class="section-title section-title--style-1 mb-0">
                <h3 class="section-title-inner heading-sm strong-600 text-uppercase">
                    Top <a href="{{episode_batch.category.get_absolute_url_cached|check_custom_domain:request}}" class="link">{{episode_batch.category.name|title}}</a> Episodes
                </h3>
            </div>

            <ul class="categories list-border--dotted">
                {% for episode in episode_batch.episodes %}
                    {% if forloop.counter < 3 %}
                        <li>
                            <a href="{{episode.get_absolute_url_cached|check_custom_domain:request}}">{{episode.name}}</a>
                        </li>
                    {% endif %}
                {% endfor %}
            </ul>
        </div>

    {% endif %}
{% endfor %}

我意识到我可以通过缓存来减少数据库负载,但在我这样做之前,我很想优化我获取这些信息的方式。目前,对于一个有 10 个类别的播客,我每页加载 70-100 次数据库。

标签: django

解决方案


在重写和进行一些较小的优化之间有一个平衡

在下面,我以剧集模型属性/模板为例进行了一些小改动。您可以走得更远,但这会使 episode_batch 完好无损

模型:

from django.db.models import Count

class Episode(models.Model):
    podcast = models.ForeignKey(Podcast, on_delete=models.CASCADE)
    categories = models.ManyToManyField(Category, blank=True)
    ...

    @property
    def get_categories(self):
        #add an annotation to the get categories so you hit the database once for all counts
        categories = self.categories.annotate(episode_count=Count('episode')).prefetch_related('episode_set')
        return categories

    @property
    def related_episodes_by_category(self):
        episodes_by_category = []
        for category in self.get_categories:
            episodes = category.episode_set.all()

            #slice the episode queryset instead of using counter in the template
            episodes_by_category.append({
                'category': category,
                'episodes': episodes[0:2],
                'count': category.episode_count,
            })
        sorted_episodes_by_category = sorted(episodes_by_category, key = lambda i: i['count'],reverse=True)
        return sorted_episodes_by_category

模板:

{% for episode_batch in episode.related_episodes_by_category %}

        <div class="sidebar-object mb-2 pb-2" id="categories">
            <div class="section-title section-title--style-1 mb-0">
                <h3 class="section-title-inner heading-sm strong-600 text-uppercase">
                    Top <a href="{{episode_batch.category.get_absolute_url_cached|check_custom_domain:request}}" class="link">{{episode_batch.category.name|title}}</a> Episodes
                </h3>
            </div>

            <ul class="categories list-border--dotted">
                {% for episode in episode_batch.episodes %}

                        <li>
                            <a href="{{episode.get_absolute_url_cached|check_custom_domain:request}}">{{episode.name}}</a>
                        </li>
                    {% endif %}
                {% endfor %}
            </ul>
        </div>

    {% endif %}
{% endfor %}

笔记:

删除了以下行,我看不到“episode_categories”的定义位置,但如果您确实需要进一步限制,请将其移至视图/属性

{% if episode_batch.category in episode_categories %}

删除了以下

{% if forloop.counter < 3 %}

推荐阅读