优化 Django 获取查询
Posted
技术标签:
【中文标题】优化 Django 获取查询【英文标题】:Optimizing Django get queries 【发布时间】:2016-07-15 12:31:04 【问题描述】:我有类似这样的 Django 代码:
for obj in some_list:
m1obj = Model1.objects.get(a=obj.a, b=obj.b, c=obj.c)
Model2(m1=m1obj, d=obj.d, e='foo').save()
我确实使用 bulk_create
优化了对 Model2
的插入,但是,由于来自 Model1
的 get
,这仍然非常缓慢(对于 3k 插入,~45sec)。
我也尝试添加:
class Meta:
index_together = [
('a', 'b', 'c'),
]
unique_together = [
('a', 'b', 'c'),
]
unique_together
有点帮助,index_together
似乎没有太大作用。
我有一个麻烦的解决方法:
-
过滤
Model1
获取我需要的所有对象,按一个或多个键排序,例如order_by('a', 'b')
,并确保 Django 缓存结果,例如len()
使用二分搜索 (from bisect import bisect_left
) 来定位第一个 a
,然后是 b
... 等等(虽然 b
s 和 c
s 的数量要少得多,所以只是迭代是一样的。李>
这将插入时间缩短到3秒!
必须有更好、更清洁和可维护的方法来做到这一点。有什么建议? 有没有办法在 Django 的缓存查询结果中过滤/获取(智能)?
编辑:将 d='foo'
更改为 d=obj.d
- 任何批量获取都需要映射到它所属的元组,否则我无法创建 Model2 条目。
【问题讨论】:
【参考方案1】:您可以进行单个查询(如 here 所述),该查询将仅获取您需要的结果,因此无需稍后进行排序和二分搜索。
我没有测试过它,所以我不知道它是否会比你已经在做的更快。此外,由于 SQL 查询将很大(根据 some_list
中的记录数),因此如果超过参数 max_allowed_packet
在 mysql 设置中定义的大小,此查询可能会引发错误(默认为 16MB,如 here 所述)。
import operator
from django.db.models import Q
query = reduce(operator.or_, (Q(a=obj.a, b=obj.b, c=obj.c) for x in values))
model1_objs = Model1.objects.filter(query)
然后你可以用Model2
做bulk_create
。
Model2.objects.bulk_create([
Model2(m1=m1, d='foo', e='bar')
for m1 in model1_objs
])
【讨论】:
不幸的是,您的建议比按顺序执行get()
要慢得多。我在 3 多分钟后停止了它。此外,正如我在编辑中提到的,如果参数“d”和“e”是恒定的,这可能(应该?)起作用。由于它们不是,我无法将model1_objs
映射到正确的obj.d
,因为数据库查询不能保证顺序。【参考方案2】:
Model1 有多少行?如果它相对较小(小于 50k),您可以使用 filter 获取所有内容,然后比较 python 中的元组。
“some_list”是小列表(小于100)怎么样,如果是你可以Q关键字一次过滤所有内容。
first = some_list.pop()
conditions = Q(a=first.a, b=first.b, c=first.c)
for obj in some_list:
conditions |= Q(a=obj.a, b=obj.b, c=obj.c)
Model1.objects.filter(conditions) # this will get your all the Model1 from ur list
Q 对象参考:https://docs.djangoproject.com/en/1.9/ref/models/querysets/#q-objects
【讨论】:
此解决方案与@muhammad-tahir 建议的相同。对于 3K 行,我在几分钟后终止了查询。对于较小的组,它并不比单独的查询快得多(根本没有)。我确实改进了创建元组到表行映射的解决方法,它既快速又可读,但如果行是 100K 而不是 3K,我可能会遇到内存问题......以上是关于优化 Django 获取查询的主要内容,如果未能解决你的问题,请参考以下文章