2014-09-03 4 views
1

У меня возникли проблемы с тем, чтобы мой SQL работал эффективно с помощью инструкции IN. Если я запускаю два оператора отдельно и вручную вставляю в серию результатов (в этом случае есть 30 vendor_id), запрос vendor_master запускается мгновенно, а запрос на счета-фактуры выполняется примерно через 2 секунды.SQL IN Statement Slowness

select * FROM invoices where vendor_id IN 

(

select vendor_id from vendor_master WHERE vendor_master_id = 12345 

); 

В чем же причина ОГРОМНОГО замедления, более 60 секунд и часто не хватает времени? Есть ли способ поместить результаты в переменную с запятыми? Или получить внутреннее утверждение для выполнения firsT?

+1

Незначительное примечание. Вам не хватает ключевое слово 'WHERE' в подзапросе –

ответ

1

В чем же причина ОГРОМНОГО замедления, более 60 секунд и часто не хватает времени?

Предложение IN работает хорошо, когда данные в состоянии IN является «малыми» и «детерминированным». Это потому, что условие оценивается один раз за строку. Итак, если предположить, что запрос в предложении IN возвращает 100 строк, а таблица в предложении FROM имеет 1000 строк, сервер должен будет выполнить 100 * 1000 = 100,000 сравнения, чтобы отфильтровать ваши данные. Слишком много усилий, чтобы фильтровать слишком мало данных, не так ли? Конечно, если ваши наборы данных (как в from, так и в in) больше, вы можете себе представить эффект.

Кстати, когда вы используете подзапрос как in состоянии, есть также дополнительные накладные расходы: подзапрос должен быть выполнен раз для каждой строки.Таким образом, последовательность что-то вроде этого:

  • строки 1
    • выполнения подзапроса
    • проверить, если значение строки 1 соответствует значению результата подзапроса
    • , если это верно, сохранить строку в результирующем наборе; исключить это иначе
  • строка 2
    • выполнить подзапросы проверку
    • если значение строки 2 соответствует значению результата подзапроса
    • , если это верно, сохранить строку в набор результатов; исключающий его иначе
  • ...

Слишком много работы, вы не думаете?


Есть ли способ, чтобы поместить результаты в переменную с запятыми?

Да, есть способ ... но вы бы действительно хотите сделать это? Давайте посмотрим:

Во-первых, создать список со значениями, которые вы хотите фильтровать:

set @valueList = (select group_concat(vendor_id separator ',') 
       from (select vendor_id from vendor_master where vendor_master_id = 12345) as a) 

Затем создайте выражение SQL:

set @sql = concat('select * from invoices where vendor_id in (', @valueList, ')'; 

Наконец, создать подготовленное заявление и выполнить его :

prepare stmt from @sql; 
execute stmt; 
-- when you're done, don't forget to deallocate the statement: 
-- deallocate prepare stmt; 

Я снова спрашиваю вас: сделать вам действительно хотите все это сделать?


Или, чтобы получить внутреннее заявление, чтобы выполнить первый?

Все остальные ответы указывают вас в правильном направлении: вместо использования in использования inner join:

select i.* 
from invoices as i 
    inner join (
     select distinct vendor_id 
     from vendor_master 
     where vendor_master_id = 12345 
    ) as vm on i.vendor_id = vm.vendor_id; 

Если по какой-то причине, это по-прежнему является слишком медленным, единственной альтернативой, которая приходит на мой взгляд, это: Создать временную таблицу (своего рода «стратегии разделяй и властвуй»):

drop table if exists temp_vm; 
create temporary table temp_vm 
    select distinct vendor_id 
    from vendor_master 
    where vendor_master_id = 12345; 
alter table temp_vm 
    add index vi(vendor_id); 
select i.* 
from invoices as i inner join temp_vm as vm on i.vendor_id = vm.vendor_id; 

Запомнить: временные таблицы видны только к соединению который создает их и удаляется при закрытии или завершении соединения.


В любом случае ваша производительность будет улучшена, если вы убедитесь, что ваши таблицы правильно проиндексированы; в частности, вам необходимо проиндексировать invoices.vendor_id и vendor_master.vendor_master_id`.

+0

благодарит за всю информацию! – Atari2600

+0

Есть ли причина, когда я запускаю операторы отдельно и создаю собственный список, разделенный запятой, который работает очень быстро, но ему все равно нужно оценить каждое значение массива IN правильно? – Atari2600

+0

рассмотрите это: для каждой строки в таблице 'invoices' условие' in' оценивается один раз, а условие 'in' - это запрос, который должен быть оценен ... слишком большая загрузка! Когда вы передаете простой список значений, разделенных запятыми, нет необходимости оценивать запрос ... тем не менее, это слишком! Используйте 'inner join' вместо' in' (чище, быстрее и дешевле) – Barranka

3

До MySQL 5.6.6, in был оптимизирован довольно неэффективно. Используйте exists вместо:

select * 
FROM invoices i 
where exists (select 1 
       from vendor_master vm 
       where i.vendor_id = vm.vendor_id and vm.vendor_master_id = 12345 
      ); 

Для лучшей производительности, вы хотите индекс на vendor_master(vendor_id, vendor_master_id).

+1

Не говоря уже о проблеме, если vendor_id имеет значение null –

1

Вы можете попробовать использовать INNER JOIN:

select i.* 
FROM invoices i 
INNER JOIN vendor_master vm 
     ON i.vendor_id = vm.vendor_id AND vm.vendor_master_id = 12345 
1

Вы можете использовать JOIN с DISTINCT вместо IN:

SELECT * 
FROM invoices JOIN 
(
    SELECT DISTINCT vendor_id as vid 
    FROM vendor_master 
    WHERE vendor_master_id = 12345 
) vmi 
ON invoices.vendor_in = vmi.vid 

Помните, что вы должны иметь DISTINCT, в противном случае, если есть две записи для внутренний запрос, чем после повторных строк после JOIN, и результат будет отличаться от запроса IN.