在 Postgres DB 中使用大量 id 的 Django 查询过滤器
Posted
技术标签:
【中文标题】在 Postgres DB 中使用大量 id 的 Django 查询过滤器【英文标题】:Django query filter using large array of ids in Postgres DB 【发布时间】:2018-03-25 20:46:42 【问题描述】:我想将 Django 中的查询传递给我的 PostgreSQL 数据库。当我使用大量 id 过滤查询时,查询速度非常慢,最多 70 秒。
在寻找答案后,我看到 this post 可以解决我的问题,只需将 IN 语句中的 ARRAY [ids]
更改为 VALUES (id1), (id2), ...
。
我在 pgadmin 中使用原始查询测试了该解决方案,查询从 70 秒到 300 毫秒...
如何在 Django 中执行相同的命令(即不使用 id 数组,而是使用 VALUES 进行查询)?
【问题讨论】:
您能发布您实际的 django 过滤器查询吗? 【参考方案1】:我使用custom lookup 找到了基于@erwin-brandstetter 答案的解决方案
from django.db.models import Lookup
from django.db.models.fields import Field
@Field.register_lookup
class EfficientInLookup(Lookup):
lookup_name = "ineff"
def as_sql(self, compiler, connection):
lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params
return "%s IN (SELECT unnest(%s))" % (lhs, rhs), params
这允许像这样过滤:
MyModel.objects.filter(id__ineff=<list-of-values>)
【讨论】:
将列表传递给标准的__in
查找时,Django 2.1 将其转换为IN (x, y, z)
。此外,问题中引用的帖子已更新为“只需要在 9.0 上应用。从 Postgres 9.3 开始,ANY(ARRAY[...]) 再次正常执行。”所以这可能只与旧版本的 Django 和 Postgres有关。
但是很高兴看到可以用作其他工作模板的自定义查找工作!
好吧,我认为这对于大型列表来说仍然是一个问题。对于包含 2k 个条目的列表,我可以通过上面的查找将查询时间从 2 秒减少到 0.05 秒!我正在使用 postgres 10 和 django 2.1
这绝对是重要的。导致使用 ARRAY 进行 SQL 查询的 ORM 调用是什么?
我似乎无法复制这一点。 2k 列表需要 15 毫秒,50k (!) 列表需要 250 毫秒。【参考方案2】:
诀窍是将 数组 转换为 set 某种方式。
而不是(这种形式只适用于短数组):
SELECT *
FROM tbl t
WHERE t.tbl_id = ANY($1);
-- WHERE t.tbl_id IN($1); -- equivalent
$1
为数组参数。
您仍然可以像以前一样传递 数组,但要取消嵌套并加入。喜欢:
SELECT *
FROM tbl t
JOIN unnest($1) arr(id) ON arr.id = t.tbl_id;
或者你也可以保留你的查询,但是用一个取消嵌套的子查询替换数组:
SELECT * FROM tbl t
WHERE t.tbl_id = ANY (SELECT unnest($1));
或者:
SELECT * FROM tbl t
WHERE t.tbl_id IN (SELECT unnest($1));
与通过 VALUES
表达式传递 set 的性能效果相同。但是传递数组通常要简单得多。
详细解释:
IN vs ANY operator in PostgreSQL How to use ANY instead of IN in a WHERE clause with Rails? Optimizing a Postgres query with a large IN【讨论】:
【参考方案3】:这是你问的第一件事的一个例子吗?
relation_list = list(ModelA.objects.filter(id__gt=100))
obj_query = ModelB.objects.filter(a_relation__in=relation_list)
这将是一个“IN”命令,因为您首先通过将 relation_list
转换为 list
来评估它,然后在您的第二个查询中使用它。
如果你做同样的事情,Django 只会做一个查询,并为你做 SQL 优化。所以这样应该会更有效率。
如果您对幕后发生的事情感到好奇,您总是可以看到您将使用 obj_query.query
执行的 SQL 命令。
希望能回答这个问题,如果没有,抱歉。
【讨论】:
【参考方案4】:让自定义查找“无效”工作时遇到了很多麻烦。 我可能已经解决了它,但我希望得到 Django 和 Postgres 专家的一些验证。
1) 在 ForeignKey 字段 (ModelB) 上“直接”使用它
ModelA.objects.filter(ModelB__ineff=queryset_ModelB)
抛出以下异常: “相关字段查找无效:ineff”
ForeignKey 字段不能用于自定义查找。
这里报告了一个类似的问题: Custom lookup is not being registered in Django
2) 在相关模型 (ModelB.id) 的 pk 字段中“间接”使用它
ModelA.objects.filter(ModelB__id__ineff=queryset_ModelB.values_list('id', flat=True))
抛出以下异常: “只能将列表(不是“元组”)连接到列表中”
查看 Django Traceback,我注意到 rhs_params 是一个元组。 然而,我们尝试在自定义查找中将其添加到 lhs_params(一个列表)。
因此我改变了:
params = lhs_params + rhs_params
进入:
params = lhs_params + list(rhs_params)
3) 然后我得到一个 Postgres 错误(至少我通过了 Django ORM) “函数unnest(uuid)不存在” “提示:没有函数与给定的名称和参数类型匹配。您可能需要添加显式类型转换。”
我显然通过更改 sql 解决了它:
来自:
return "%s IN (SELECT unnest(%s))" % (lhs, rhs), params
到:
return "%s IN (SELECT unnest(ARRAY(%s)))" % (lhs, rhs), params
因此我的最终 as_sql 方法如下所示:
def as_sql(self, compiler, connection):
lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + list(rhs_params)
return "%s IN (SELECT unnest(ARRAY(%s)))" % (lhs, rhs), params
它似乎有效,并且确实比 in__ 更快(在 Postgres 中使用 EXPLAIN ANALYZE 进行了测试)。 但我很想得到专家的一些验证,也许是 Erwin Brandstetter? 感谢您的意见。
【讨论】:
以上是关于在 Postgres DB 中使用大量 id 的 Django 查询过滤器的主要内容,如果未能解决你的问题,请参考以下文章
使用 SQL 列出 Postgres db 8.1 中的所有序列