使用大型列表优化 Django 查询集调用?

Posted

技术标签:

【中文标题】使用大型列表优化 Django 查询集调用?【英文标题】:Optimizing Django queryset calls, with large lists? 【发布时间】:2017-08-09 17:01:02 【问题描述】:

我会尽力解释我的问题。我遇到了性能问题,我想知道是否有更好的设置方法。

我有一个数据库,其中包含大约 150 年的逐年数据。每行大约有 10 列。

我每 30 年跨度运行一次“模拟”(我们将每个 30 年的块称为“周期”)。因此,第 1 周期将是 1-31 年。第 2 周期将是 2-32 年。第 3 周期,第 3-33 年。明白了吗?

all_data = DataPoint.objects.all().yearly()
cycle_length = 30
previous_data_points = []
for data_point in all_data:
    if len(previous_data_points) < cycle_length:
        previous_data_points.append(data_point)
        continue
    simulation_cycles.append(Cycle(previous_data_points))
    if len(previous_data_points) == cycle_length:
        previous_data_points.pop(0)
    previous_data_points.append(data_point)

因此,对于每个 30 年周期,我向 Cycle 函数提供 30 个查询集项以初始化数据。问题是,当我使用 Django 的 connection.queries 列出正在发生的事情时,看起来它正在执行 3000 多个查询并且需要 10-12 秒,这对于它正在做的事情来说相当长。

在 connection.queries 列表中,当我将它传递给 Cycle 时,我看到它正在执行 30 个单独的调用(每个数据点 1 个)(每个调用都使用“WHERE EXTRACT(MONTH FROM”,我相信这是我的 .yearly( ) 过滤器被调用。但它也在 Cycle() 中记录了一个查询,它实际上是按日期查找数据点。

当我像这样传递部分查询集时,数据是否被缓存?我的应用程序真的在运行 3000 个查询(使用数据库上的连接)还是在我的 DataPoint.objects.all().yearly() 调用中运行 1 个大查询,而其他所有查询都在内存中?

我试图了解为什么它运行得如此缓慢。我认为,部分问题是我正在创建这个庞大的对象列表:120 个“周期”,每个周期都有 30 个单独的“年”对象,这些对象具有自己的数据(以及用于生成该数据的计算)以供以后使用。内存中有这么多对象会伤害我,还是小土豆?

编辑:

class Cycle:
def __init__(self, data_points):
    self.range_start = data_points[0].data_date
    self.range_end = data_points[-1].data_date
    self.start_CPI = data_points[0].cpi
    self.years = relativedelta(self.range_end, self.range_start).years + 1
    self.sim = []

    for i in range (0, self.years):
        data_point = data_points[i]
        self.sim.append(Segment(
            date=self.range_start + relativedelta(years=i),
            start_CPI=self.start_CPI,
            yearly_equities_growth=data_point.yearly_equities_growth,
            cpi=data_point.cpi,
            dividend=data_point.dividend,
            s_and_p_composite=data_point.s_and_p_composite,
            long_interest_rate=data_point.long_interest_rate
        ))

class Segment:
def __init__(self, date, start_CPI, yearly_equities_growth, cpi, dividend, s_and_p_composite, long_interest_rate):
    self.start_CPI = D(start_CPI)
    self.date = date
    self.portfolio = 
        "start": None,
        "end": None,
        "inflation_adjusted_start": None,
        "inflation_adjusted_end": None,
        "fees": None
    
    self.spending = None
    self.inflation_adjusted_spending = None
    self.equities = 
        "start": None,
        "growth": None,
        "val": None
    
    self.bonds = 
        "start": None,
        "growth": None,
        "val": None
    
    self.gold = 
        "start": None,
        "growth": None,
        "val": None
    
    self.cash = 
        "start": None,
        "growth": None,
        "val": None
    
    self.dividends = 
        "growth": None,
        "val": None
    
    self.fees = None

    self.yearly_equities_growth = D(yearly_equities_growth)
    self.cumulative_inflation = 1 + (D(cpi) - self.start_CPI) / self.start_CPI
    self.sum_of_adjustments = None
    self.cpi = cpi
    self.dividend = dividend
    self.s_and_p_composite = s_and_p_composite
    self.long_interest_rate = long_interest_rate

【问题讨论】:

能分享一下 Cycle 类的代码吗?更好地了解您的问题 完成。 Cycle() 也调用 Segment() 所以我添加了它。每个周期有 30 个段。 【参考方案1】:

遍历 QuerySet 应该在第一次迭代时查询所有内容,然后将缓存的结果用于后续迭代 (source)。我不熟悉connection.queries,但是

您可以使用 django-debug-toolbar、django-extensions 或 django Silk 对 3000 多个查询假设 (source) 进行健全性检查。

@jperelli 建议 `data_point = data_points[i] 可能是罪魁祸首。

for i in range (0, self.years):
    data_point = data_points[i]

如果在将 all_data 传递给迭代器之前将其转换为列表会发生什么?

【讨论】:

【参考方案2】:

根据您的代码,在我看来这部分是在做 O(n²) 算法

for i in range (0, self.years):

如果在 for 中运行的任何代码需要进入数据库,那就是问题所在。

检查是否有任何这些字段正在访问数据库。如果其中任何一个是相关字段,则需要 prefetch_related 或 select_related。或者,其中任何一个都是需要访问数据库的属性,请检查一下。

data_point.yearly_equities_growth,
data_point.cpi,
data_point.dividend,
data_point.s_and_p_composite,
data_point.long_interest_rate

另外,D() 函数在做什么?它可能会强制查询数据库,检查一下。

【讨论】:

那些属性确实命中了数据库,但我假设当我传入data_point 时,它已经有了数据。 D() 只是from decimal import Decimal as D 我不熟悉prefetch_related or select_related,我会检查他们并报告。 prefetch_related 和 select_related 在您使用与其他表有关系的属性的情况下提供帮助 我明白了。该表实际上没有任何关系。这是一个非常简单的数据存储。如果一开始,我将 all_data = DataPoint.objects.all().yearly() 更改为 cached_data = [] all_data = DataPoint.objects.all().yearly() for a in all_data: cached_data.append(a) 然后我可以使用 cached_data[] 来填充 Cycle() 和 Segment() 对象。我认为这会提高数据库效率? 可能就是这样。也许data_point = data_points[i] 强制查询

以上是关于使用大型列表优化 Django 查询集调用?的主要内容,如果未能解决你的问题,请参考以下文章

django查询集

Django查询集QuerySet及两大特性

Django 管理员更改列表优化查询:选择 field1,field2 而不是 select *

当查询被填充时,有没有办法在 Django 模板中呈现大型查询集?

如何在返回的 AJAX 调用上使用 django 模板标签?

Django中模型