【问题标题】:Page loading is very slow - how to properly optimize?页面加载很慢——如何正确优化?
【发布时间】:2017-01-06 16:08:58
【问题描述】:

我正在处理一个包含不同产品统计信息的大页面。我在那里使用多个图表、表格等。加载页面需要例如 5 秒。

这是一个非常简化的models.py

编辑

例如,OccurencesTable 包含此行:

last_scan_time = tables.columns.TemplateColumn("""{{ record.get_last_scan.datetime }}""",accessor='get_last_scan.datetime', verbose_name='Last scan time')

因此,对于每一行,都必须对调用最后一次扫描的数据库执行查询。更好的方法是预加载所有 Scan 对象。

产品

class Product(models.Model):
    user = models.ForeignKey(User, null=False, blank=False, related_name='products')
    name = models.CharField(max_length=200)

    def get_occurences(self):
        return self.occurences.prefetch_related("scans__price__currency")

    def get_occurences_count(self):
        return self.occurences.all().count()

    def get_all_scans(self):
        return [item for sublist in [x.scans.all() for x in self.get_occurences()] for item in sublist]

    def get_all_valid_scans(self):
        return [item for sublist in [x.get_valid_scans() for x in self.get_occurences()] for item in sublist]

    def get_last_scans(self):
        scans = []
        for occ in self.get_occurences():
            scan = occ.get_last_scan()
            if scan:
                scans.append(scan)
        return scans

   # @property
    #def last_scan_time(self):
        #scans = sorted([x for x in self.get_last_scans() if x.datetime],key = lambda x: x.datetime, reverse=True)
        #Scan.objects.filter(occurence__product=self,price__amount__isnull=False)
        #return str(scans[0].datetime)

    def get_last_min_scan(self):

        sorted_last_scans = [x for x in self.get_last_scans() if x.valid]
        sorted_last_scans.sort(key=lambda x: x.price.eur_price)
        return sorted_last_scans[0] if sorted_last_scans else None

    def get_last_min_price(self):
        last_scan = self.get_last_min_scan()
        if last_scan:
            return last_scan.price.eur_price
        return None

    def get_active_occurences(self):
        return self.get_occurences().filter(active=True)

发生率

class Occurence(models.Model):
    product = models.ForeignKey(Product, related_name='occurences', on_delete=models.CASCADE)

    _last_scan = models.OneToOneField('Scan',null=True,blank=True,related_name='+')


    currency = models.ForeignKey('Currency',related_name='occurences')

    def get_last_scan(self):
        try:
            last = self.scans.select_related("price__amount").order_by('datetime').last()
        except:
            last = None
        return last

    def get_last_valid_scan(self):
        try:
            last = self.scans.exclude(price__isnull=True).order_by('-datetime').first()
        except:
            last = None
        return last

    def get_second_last_valid_scan(self):
        scans = self.scans.exclude(price__isnull=True).order_by('-datetime').select_related("price")
        if scans.count()>=2:
            return scans[1]
        return None

    def get_valid_scans(self):
        return self.scans.all().exclude(price__isnull=True)

    def get_min_scan(self):
        scan = self.get_valid_scans().order_by('price__amount').first()
        if scan:
            return scan
        return None

    """ STATS METHODS """

    def stats_get_difference_for_two_last_scans(self):
        second_last_valid_scan = self.get_second_last_valid_scan()
        if second_last_valid_scan:
            difference_in_percent = math_ops.round_eur(decimal.Decimal(-1 * (100 - self.get_last_valid_scan().price.eur_price / second_last_valid_scan.price.eur_price * 100), 2))
        else:
            difference_in_percent = decimal.Decimal(0)
        return {'percent':difference_in_percent,
                'xml_tag':'<two_last_scans_difference_in_percent>',
                'xml_close_tag':'</two_last_scans_difference_in_percent>',
                'label':'Last scans diff'}

    def stats_get_min_price(self):
        scan = self.get_min_scan()
        if scan:
            price = scan.price.eur_price
        else:
            price = None
        return {'price': price,
                'xml_tag': '<min_price>',
                'xml_close_tag': '</min_price>',
                'label': 'Min'}

    def stats_get_avg_price(self):
        prices = [x.price for x in self.scans.all() if x.price]
        if prices:
            price = math_ops.round_eur(decimal.Decimal(sum([x.eur_price for x in prices]) / len(prices), 2))
        else:
            price = None

        preferred_currency = self.product.user.userprofile.preferred_currency

        if preferred_currency:
            if preferred_currency.shortcut == 'czk':
                amount = Exchange.eur_to_czk(price)
                pref_currency_string = '{} CZK'.format(amount)
                pref_currency_amount = amount
            else:
                amount = price
                pref_currency_string = u'{} €'.format(amount)
                pref_currency_amount = amount
        else:
            if self.currency.shortcut == 'czk':
                amount = Exchange.eur_to_czk(price)
                pref_currency_string = '{} CZK'.format(amount)
                pref_currency_amount = amount
            else:
                amount = price
                pref_currency_string = u'{} €'.format(amount)
                pref_currency_amount = amount


        return {'price': price,
                'pref_currency_string':pref_currency_string,
                'pref_currency_amount':pref_currency_amount,
                'xml_tag': '<average_price>',
                'xml_close_tag': '</average_price>',
                'label': 'AVG'}

价格

class Price(models.Model):
    currency = models.ForeignKey('Currency',related_name='prices')
    amount = models.DecimalField(max_digits=10,decimal_places=2)


    def __unicode__(self):
        return u'{} {}'.format(self.amount,self.currency)

    def to_eur(self):
        if self.currency.shortcut=='eur':
            return self.amount
        elif self.currency.shortcut=='czk':
            return Exchange.objects.first().czk_to_eur(self.amount)

    def to_czk(self):
        if self.currency.shortcut == 'czk':
            return self.amount
        elif self.currency.shortcut == 'eur':
            return Exchange.objects.first().eur_to_czk(self.amount)

    @property
    def eur_price(self):
        if self.currency.shortcut=='eur':
            return self.amount
        elif self.currency.shortcut=='czk':
            return self.to_eur()

    @property
    def czk_price(self):
        cents = decimal.Decimal('01')
        if self.currency.shortcut == 'czk':
            return (self.amount).quantize(cents, decimal.ROUND_HALF_UP)
        elif self.currency.shortcut == 'eur':
            return self.to_czk()

    @property
    def pref_currency_amount(self):
        pref_currency = self.scan.occurence.product.user.userprofile.preferred_currency
        if pref_currency:
            if pref_currency.shortcut == 'czk':
                return self.czk_price
            else: return self.eur_price
        return self.amount

    @property
    def pref_currency_string(self):
        pref_currency = self.scan.occurence.product.user.userprofile.preferred_currency
        # return pref_currency.shortcut
        if pref_currency:
            if pref_currency.shortcut.lower() == 'czk':
                return u'{} {}'.format(self.czk_price, pref_currency.shortcut)
            else:
                return u'{} {}'.format(self.eur_price, pref_currency.special_sign)
        return u'{} {}'.format(self.amount,self.currency.special_sign)


    def get_price(self,currency):
        if currency=='eur':
            return self.eur_price
        elif currency=='czk':
            return self.czk_price

    def get_exchanged_price_string(self):
        if self.currency.shortcut=='czk':
            return u'{} {}'.format(Exchange.czk_to_eur(self.amount),u'€')
        else:
            return u'{} {}'.format(Exchange.eur_to_czk(self.amount),'CZK')

    def get_original_price_string(self):
        if self.currency.shortcut=='czk':
            return u'{} {}'.format(self.amount,u'€')
        else:
            return u'{} {}'.format(Exchange.eur_to_czk(self.amount),'CZK')

例如根据django-debug-toolbar 渲染表occurences 需要将近2 秒。我正在尝试使用select_relatedprefetch_related 对其进行优化,但它仍然很慢。

这是因为我调用的不同方法具有相同的查询,并且这些查询被多次调用。

class OccurencesTable(tables.Table):
    site = tables.columns.TemplateColumn("""<a href="{{ record.url }}">{{ record.site.name }}</a>""",accessor='site.name', verbose_name=u'Site')
    avg_price = tables.columns.TemplateColumn("""{{ record.stats_get_avg_price.pref_currency_string }}""",accessor='stats_get_avg_price.price', verbose_name='AVG price')
    last_scan_price = tables.columns.TemplateColumn("""{{ record.get_last_scan.price.pref_currency_string }} """,accessor='get_last_scan.price.amount', verbose_name='Last scan price')
    last_scan_time = tables.columns.TemplateColumn("""{{ record.get_last_scan.datetime }}""",accessor='get_last_scan.datetime', verbose_name='Last scan time')
    difference = tables.columns.TemplateColumn("""{% load static %}{% with diff=record.stats_get_difference_for_two_last_scans.percent %}
                {% if diff > 0 %}+{% endif %}{{ diff }} % <img style="height: 15px" src="{% if diff < 0 %}{% static "img/icons/arrow-trend-minus.png" %}{% elif diff == 0 %}{% static "img/icons/arrow-trend-normal.png" %}{% else %}{% static "img/icons/arrow-trend-plus.png" %}{% endif %}">
                {% endwith %}""",verbose_name='Difference')

    class Meta:
        model = Occurence
        fields = ('id', 'site', 'last_scan_time','last_scan_price', 'difference',  'avg_price')
        attrs = {'id': 'id_occurences_table',
                 'class': 'table', }

不知道如何优化模型OccurenceProduct的方法。你有什么想法吗?

【问题讨论】:

    标签: python django django-models django-database sql-optimization


    【解决方案1】:

    有了这样的代码,你应该对你得到的时机感到兴奋

    @property
    def last_scan_time(self):
        scans = sorted([x for x in self.get_last_scans() if x.datetime],key = lambda x: x.datetime, reverse=True)
        Scan.objects.filter(occurence__product=self,price__amount__isnull=False)
        return str(scans[0].datetime)
    

    此代码通过调用 get_last_scans() 检索整个表,然后您在 python 代码中对该结果进行排序!数据库具有非常快速的内置排序功能。请使用它。

    在这段代码中还有很多类似的函数。您将不得不修复它们中的每一个。在数据库中进行过滤和排序。不在你的 python 代码中。

    【讨论】:

    • 是的,这个有一个简单的解决方案(我忘记改了),但是这个没有用在表格中,主要问题是优化 Occurence 模型的方法,很抱歉把这个方法留在那里。
    • 请发布一个新问题,其中包含导致问题的特定视图、模型和模板。换句话说,创建一个MCVE
    • 好的,我已经创建了一个 MCVE 并进行了测试。加载页面大约需要 3 秒,渲染模板大约需要 2 秒。这里是主题:stackoverflow.com/questions/41510364/optimize-django-page-load
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-13
    • 1970-01-01
    • 2015-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多