我粗略的模特:
class m_Interaction(models.Model):
fk_ip = models.ForeignKey('m_IP', on_delete=models.SET_NULL, null=True, related_name="interactions")
fk_query = models.ForeignKey('m_Query', on_delete=models.SET_NULL, null=True, related_name="interactions")
使用的数据库:SQLite
如果我执行此查询集
m_Interaction.objects.filter(fk_query=None).filter(fk_ip__in=user.ips.all()).select_related('fk_query')
需要5秒钟。
如果删除filter(fk_query=None)
语句,则删除剩余的查询集
m_Interaction.objects.filter(fk_ip__in=user.ips.all()).select_related('fk_query')
仅在100毫秒内执行。
filter(fk_ip__in=user.ips.all())
不应该贵得多吗?或者至少为什么filter(fk_query=None)
语句如此之慢?它应该是一个简单的“与Null比较”-lookup。
使用filter(fk_query=None)
的SQL查询:
SELECT "data_manager_m_interaction"."id",
"data_manager_m_interaction"."fk_ip_id",
"data_manager_m_interaction"."fk_query_id",
"data_manager_m_query"."id",
"data_manager_m_query"."fk_ip_id"
FROM "data_manager_m_interaction"
LEFT OUTER JOIN "data_manager_m_query"
ON ("data_manager_m_interaction"."fk_query_id" = "data_manager_m_query"."id")
WHERE ("data_manager_m_interaction"."fk_ip_id" IN (SELECT U0."id" FROM "data_manager_m_ip" U0 WHERE U0."fk_user_id" = 1339)
AND "data_manager_m_interaction"."fk_query_id" IS NULL)
ORDER BY "data_manager_m_interaction"."timestamp" ASC
LIMIT 1
没有filter(fk_query=None)
的SQL查询:
SELECT "data_manager_m_interaction"."id",
"data_manager_m_interaction"."fk_ip_id",
"data_manager_m_interaction"."fk_query_id",
"data_manager_m_query"."id",
"data_manager_m_query"."fk_ip_id"
FROM "data_manager_m_interaction"
LEFT OUTER JOIN "data_manager_m_query"
ON ("data_manager_m_interaction"."fk_query_id" = "data_manager_m_query"."id")
WHERE "data_manager_m_interaction"."fk_ip_id" IN (SELECT U0."id" FROM "data_manager_m_ip" U0 WHERE U0."fk_user_id" = 1339)
ORDER BY "data_manager_m_interaction"."timestamp" ASC
LIMIT 1
EXPLAIN QUERY PLAN(带过滤器):
[(0, 0, 0, 'SEARCH TABLE data_manager_m_interaction USING INDEX data_manager_m_interaction_c50f4040 (fk_query_id=?)'),
(0, 0, 0, 'EXECUTE LIST SUBQUERY 1'),
(1, 0, 0, 'SEARCH TABLE data_manager_m_ip AS U0 USING COVERING INDEX data_manager_m_ip_f569ccde (fk_user_id=?)'),
(0, 1, 1, 'SEARCH TABLE data_manager_m_query USING INTEGER PRIMARY KEY (rowid=?)'),
(0, 0, 0, 'USE TEMP B-TREE FOR ORDER BY')]
EXPLAIN QUERY PLAN(不带过滤器)
[(0, 0, 0, 'SEARCH TABLE data_manager_m_interaction USING INDEX data_manager_m_interaction_c669518a (fk_ip_id=?)'),
(0, 0, 0, 'EXECUTE LIST SUBQUERY 1'),
(1, 0, 0, 'SEARCH TABLE data_manager_m_ip AS U0 USING COVERING INDEX data_manager_m_ip_f569ccde (fk_user_id=?)'),
(0, 1, 1, 'SEARCH TABLE data_manager_m_query USING INTEGER PRIMARY KEY (rowid=?)'),
(0, 0, 0, 'USE TEMP B-TREE FOR ORDER BY')]
答案 0 :(得分:1)
sqlite和mysql的问题在于它们每个表只能使用一个索引,如https://www.sqlite.org/optoverview.html所述
查询的FROM子句中的每个表最多只能使用一个索引 (当OR子句优化发挥作用时除外)和SQLite 努力在每个表上使用至少一个索引
它变得更糟,因为sqlite查询解析器将ON条件转换为WHERE子句。即使没有IS NULL
你的WHERE
条款也相当沉重。它变得更糟,因为你有一个订单。
SQLite尝试使用索引来满足a的ORDER BY子句 尽可能查询。当面临使用索引的选择时 满足WHERE子句约束或满足ORDER BY子句, SQLite执行上述相同的成本分析并选择 它相信的索引会得到最快的答案。
在很多情况下,mysql可以使用另一个索引,但是sqlite不能。 Postgresql,可以说是最好的开源RDBMS可以在每个表上使用多个索引。
因此,简而言之,sqlite无法使用索引进行IS NULL
比较。在查询中使用EXPLAIN
会显示可用索引用于fk_ip_id
修改强>:
我对sqlite解释输出的熟练程度不如我在postgresql或mysql上,但据我所知,它表明每个表使用一个索引,如上所述。 data_manager_m_ip
表是充分利用索引的表。在那里,表本身甚至没有查看从索引本身检索的所有数据。
该解释也表明使用了fk_query_id上的索引。但是我的理解是这用于连接。该解释还表明,没有任何索引用于排序。你也可以发布其他查询的解释。
编辑2 :
在那里,没有看EXPLAIN
而优化是危险的。我们猜测它是空的比较慢。但它不是!!当您进行IS NULL
比较时,sqlite会使用索引,但IN
子句现在没有索引,这使得它非常慢!!
解决方案:您需要fk_query_id, fk_ip_id
的综合索引,您可以使用django index_together制作一个。