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

select * FROM invoices where vendor_id IN

(

select vendor_id from vendor_master WHERE vendor_master_id = 12345

);

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

1
Atari2600 3 Сен 2014 в 06:01

4 ответа

Лучший ответ

Так что же вызывает ОГРОМНОЕ замедление, более 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` проиндексированы.

1
Barranka 23 Сен 2014 в 20:36

До 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).

3
Gordon Linoff 3 Сен 2014 в 02:03

Вы можете попробовать использовать 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
xdazz 3 Сен 2014 в 02:07

Вы можете использовать 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 запрос.

1
Mohammad 3 Сен 2014 в 02:24