Laravel orWhere() / MySQL или запрос, занимающий много времени

Я использую Laravel 4.2, и мое приложение используется для отслеживания запасов в нескольких местах.

База данных настроена с таблицей inventory_items, таблицей inventory_locations и сводной таблицей между ними inventory_items_inventory_location, которая содержит значения количества, ссылаясь как на элемент инвентаря, так и на местоположение, которому принадлежит запись.

Мой запрос состоит в том, чтобы найти элементы инвентаря, у которых любое значение количества местоположения больше или равно 0. В Laravel я использую подзапрос и orWhere следующим образом:

InventoryItem::whereHas('inventoryLocations', function($q) {
  $q->where('reserved', '>=', 0)
     ->orWhere('available', '>=', 0) # slow
     ->orWhere('inbound', '>=', 0) # slow
     ->orWhere('total', '>=', 0); # slow
})->toSql();

Что дает следующий SQL:

select * from `inventory_items`
where `inventory_items`.`deleted_at` is null
and (
  select count(*) from `inventory_locations`
  inner join `inventory_item_inventory_location`
  on `inventory_locations`.`id` = `inventory_item_inventory_location`.`inventory_location_id` 
  where `inventory_item_inventory_location`.`inventory_item_id` = `inventory_items`.`id`
  and `reserved` >= ?
  or `available` >= ? # slow
  or `inbound` >= ? # slow
  or `total` >= ? # slow
) >= 1

Проблема в том, что с операторами or (отмеченными в коде #slow) время запроса составляет до 1 с непосредственно с Sequel Pro, более 5 с через мое приложение Laravel (или через ремесленника). Без этих проверок «или» (т. е. просто проверки одного типа количества, например, «зарезервировано») запрос составляет ‹100 мс в Sequel Pro и аналогичен в приложении/мастере.

Я не уверен, почему добавление этих дополнительных проверок «или» добавляет так много времени к запросу. Любые идеи, как сделать более производительный запрос?


person Mr Office    schedule 30.12.2016    source источник
comment
Вы добавили индексы в поля таблицы reserved, available, inbound, total?   -  person num8er    schedule 30.12.2016
comment
В общем случае или условие составляют возможности выполнения запроса. Также механизм базы данных никогда не создает статический путь, когда вы используете динамические условия такого типа. Так что это может занять больше времени, чем для каждого подготовленного пути   -  person Shankar Thiyagaraajan    schedule 30.12.2016
comment
@ num8er да, у меня есть индексы для каждого, и я тоже пробовал множественный индекс (не уверен, что это правильная терминология)   -  person Mr Office    schedule 30.12.2016
comment
@ShankarThiyagaraajan не на 100% уверен, что вы имеете в виду - не могли бы вы привести простой пример?   -  person Mr Office    schedule 30.12.2016
comment
Сколько строк в вашей таблице? Кроме того, правильно ли я предполагаю, что у вас могут быть отрицательные значения для ваших available, inbound и total?   -  person Rwd    schedule 30.12.2016
comment
@MrOffice, если вы уже добавили индексы для каждого поля, поэтому я могу предположить, что вам нужно изменить логику сравнения с >= на >, потому что я не вижу логики whereHas('inventoryLocations'). Я имею в виду, что вам нужно проверить существование inventoryLocations, если данные больше нуля. потому что >= может вернуть все данные.   -  person num8er    schedule 30.12.2016
comment
@RossWilson Всего 5 тыс. строк. Допускаются отрицательные значения, но в настоящее время их нет.   -  person Mr Office    schedule 30.12.2016
comment
@num8er Спасибо - изменение в сравнении ни на что не влияет - я пробовал с =, >, < и т. д.   -  person Mr Office    schedule 30.12.2016
comment
Спасибо, ребята, @jedzrej.kurylo попал в точку.   -  person Mr Office    schedule 30.12.2016
comment
@Mr Office, в общем, где класс сделает пошаговый фильтр уровня. Но orWhere немного отличается. Процесс фильтрации полностью изменится с предыдущим фильтром или без него (предыдущий Где). Таким образом, каждый раз, когда вы используете orWhere, он будет динамически изменять свой поток. Так что это может занять заметное время задержки.   -  person Shankar Thiyagaraajan    schedule 31.12.2016
comment
@Мистер Офис, бывший. Считай, Студенческий Список. Всего 100 записей в виде Имя|Имя|Фамилия|Электронная почта. Запрос: выберите * из студентов, где фамилия, например, «%john%», или электронная почта, например, «%sales%»; Здесь генерируются 3 возможных вероятности: 1. Фамилия, например «%john%», 2. Электронная почта, например «%sales%», 3. Фамилия, например «%john%» или электронная почта, например «%sales%». Эти три будут запущены динамически. !   -  person Shankar Thiyagaraajan    schedule 31.12.2016


Ответы (1)


Просмотрите результирующий запрос и его условия WHERE. Вы определенно пропустили некоторые скобки, так как я думаю, что вам нужно

where `inventory_item_inventory_location`.`inventory_item_id` = `inventory_items`.`id`
and (
   `reserved` >= ?
   or `available` >= ? #
   or `inbound` >= ?
   or `total` >= ?
)

вместо

where `inventory_item_inventory_location`.`inventory_item_id` = `inventory_items`.`id`
and `reserved` >= ?
or `available` >= ? # slow
or `inbound` >= ? # slow
or `total` >= ?

Это приводит к полному сканированию таблицы, что ужасно медленно для таблиц с большим количеством строк.

Чтобы исправить это, замените

InventoryItem::whereHas('inventoryLocations', function($q) {
  $q->where('reserved', '>=', 0)
   ->orWhere('available', '>=', 0) # slow
   ->orWhere('inbound', '>=', 0) # slow
   ->orWhere('total', '>=', 0); # slow
})->toSql();

с

InventoryItem::whereHas('inventoryLocations', function($q) {
  $q->where(function($subquery) {
    $subquery->where('reserved', '>=', 0)
     ->orWhere('available', '>=', 0)
     ->orWhere('inbound', '>=', 0)
     ->orWhere('total', '>=', 0);
  });
})->toSql();

Ознакомьтесь с командой MySQL EXPLAIN, которая позволяет проанализировать, как будет выполняться запрос и сколько строк будет запрошено — http://dev.mysql.com/doc/refman/5.7/en/explain.html

person jedrzej.kurylo    schedule 30.12.2016
comment
Отлично - имеет смысл сделать подзапрос, теперь я знаю немного больше о том, как это работает. В будущем мы рассмотрим объяснение, чтобы отладить такого рода проблему. Спасибо - person Mr Office; 30.12.2016