看似快速的过滤器字段查找很慢

时间:2016-06-03 19:19:45

标签: python django sqlite django-queryset django-filter

我粗略的模特:

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')]

1 个答案:

答案 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制作一个。