2016-06-03 2 views
1

Моей грубая модель:Казалось бы, быстрый фильтр поле поиска медленно

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.


SQL-запросов с filter(fk_query=None):

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 

SQL-запросов без filter(fk_query=None):

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 план запроса (с фильтром):

[(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 ПЛАН (без фильтра)

[(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

Во-первых, вы можете попробовать 'filter (fk_query__isnull = True)' вместо 'fk_query = None' и посмотреть, улучшает ли это что-либо, но я бы выполнил' print your-query.query', чтобы увидеть исходную инструкцию sql и посмотреть разница. –

+0

Согласен, что это кажется нелогичным. Не имеет значения, измените ли вы порядок фильтров или объедините их в один фильтр? 'filter (fk_query = None, fk_ip__in = user.ips.all())' –

+0

вы используете mysql? я подозреваю, что вы. Если это так, то на самом деле это не удивительно. – e4c5

ответ

1

Проблема с SQLite и MySQL является то, что они могут использовать только один индекс для каждой таблицы, как указано в https://www.sqlite.org/optoverview.html

Каждая таблица в ЕКОМ в запросе может использоваться не более одного индекса (за исключением случаев, когда оптимизация OR-предложения входит в игру), а SQLite стремится использовать по крайней мере один индекс для каждой таблицы

И все хуже, потому что парсер sqlite-запроса преобразует условие ON в предложение WHERE. Даже без IS NULL ваша статья WHERE довольно тяжелая. И это становится хуже, потому что у вас есть заказ.

SQLite пытается использовать индекс, чтобы удовлетворить предложение ORDER BY запроса , когда это возможно. Когда столкнулся с выбором использования индекса для , удовлетворяйте ограничениям предложения WHERE или удовлетворяющим предложению ORDER BY, SQLite делает тот же анализ затрат, описанный выше, и выбирает индекс , который, по его мнению, приведет к самому быстрому ответу.

Во многих ситуациях mysql может использовать другой индекс для заказа, но sqlite не может. Postgresql, возможно, лучшая RDBMS с открытым исходным кодом может использовать несколько индексов для каждой таблицы.

Таким образом, нет никакого способа, чтобы sqlite мог использовать индекс для вашего сравнения IS NULL. используя EXPLAIN на запрос покажет, что доступный индекс используется на fk_ip_id

Edit: Я не настолько опытны в SQLite объяснить выход, как я на PostgreSQL или MySQL, но от того, что я понимаю его показывает, что каждая таблица использует один индекс, как обсуждалось выше. Таблица data_manager_m_ip - это та, которая наилучшим образом использует индексы. Там сама таблица даже не просматривается, все данные извлекаются из самого индекса.

Объяснение также показывает, что используется индекс на fk_query_id. Однако я понимаю, что это используется для соединения. Объяснение также показывает, что нет никакого использования индексов для сортировки. Можете ли вы опубликовать объяснение для другого запроса.

Edit 2: Там вы, это опасно для оптимизации, не глядя на EXPLAIN. Мы предполагали, что это было неверное сравнение, которое было медленным. но это не так! Когда вы делаете сравнение IS NULL, sqlite использует индекс для этого, но предложение IN теперь без индекса, и это делает его ужасно медленным!

Решение: Вам нужен составной индекс для fk_query_id, fk_ip_id, вы можете использовать django index_together, чтобы сделать его.

+0

Спасибо за этот подробный anser, но объясняет ли это плохую производительность 'filter (fk_query = нет) '? Если я оставлю это только и удаляю 'filter (fk_ip__in = user.ips.all())' -part, он также плохо работает. Но может быть, потому что я не создал индекс для поля fk_query? –

+0

Я не уверен, как это интерпретировать, но для меня кажется, что SQLite ищет взаимодействия, отфильтрованные query = null, используя индекс. Затем он фильтрует результат с помощью ips, также с индексом и так далее. Так что все кажется мне замечательным –

+0

Нужно ли мне переиндексации всего? Я добавил 'index_together = [" fk_query "," fk_ip "]' к модели и сделал «makemigrations» и «migrate». Потребовалось 5 минут для миграции, поэтому я подумал, что он создал индекс, но сам запрос все еще медленный. Также нет изменений в 'explain'-query, должно ли быть изменение? –