У меня возникли проблемы с тем, чтобы мой 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 секунд и часто превышающее время ожидания? Есть ли способ записать результаты в переменную через запятую? Или заставить внутренний оператор выполнить первый?
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` проиндексированы.
invoices
условие in
оценивается один раз, и это условие in
является запросом, который необходимо оценить... слишком большая нагрузка! Когда вы передаете простой список значений, разделенных запятыми, нет необходимости оценивать запрос... и все же это слишком! Используйте inner join
вместо in
(чище, быстрее и дешевле)
До 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)
.
Вы можете попробовать использовать 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
Вы можете использовать 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
запрос.
Похожие вопросы
Новые вопросы
mysql
MySQL — это бесплатная система управления реляционными базами данных (RDBMS) с открытым исходным кодом, которая использует язык структурированных запросов (SQL). НЕ ИСПОЛЬЗУЙТЕ этот тег для других БД, таких как SQL Server, SQLite и т. д. Это разные БД, которые используют свои собственные диалекты SQL для управления данными. В вопросе всегда указывайте точную версию сервера. Версии 5.x сильно отличаются по своим возможностям от версий 8+.
WHERE
.