使用大型列表优化 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 管理员更改列表优化查询:选择 field1,field2 而不是 select *
当查询被填充时,有没有办法在 Django 模板中呈现大型查询集?