MySQL - ВЫБЕРИТЕ, ГДЕ поле IN (подзапрос) - Чрезвычайно медленно почему?


133

У меня есть пара дубликатов в базе данных, которые я хочу проверить, поэтому, что я сделал, чтобы увидеть, какие дубликаты, я сделал это:

SELECT relevant_field
FROM some_table
GROUP BY relevant_field
HAVING COUNT(*) > 1

Таким образом, я получу все строки с релевантным полем, встречающимся более одного раза. Этот запрос занимает миллисекунды для выполнения.

Теперь я хотел проверить каждый из дубликатов, поэтому я решил выбрать каждую строку в some_table с релевантным полем в приведенном выше запросе, поэтому я сделал так:

SELECT *
FROM some_table 
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
)

Это оказывается очень медленно по какой-то причине (это занимает несколько минут). Что именно здесь происходит, чтобы сделать это так медленно? релевантное поле индексируется.

В конце концов я попытался создать представление «temp_view» из первого запроса (SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1), а затем вместо этого создать свой второй запрос:

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM temp_view
)

И это работает просто отлично. MySQL делает это за несколько миллисекунд.

Здесь есть эксперты по SQL, которые могут объяснить, что происходит?


что ты точно хочешь? Хотите удалить повторяющиеся записи, кроме одной ?? Предложение: пожалуйста, прочитайте Self Join
diEcho

1
очевидно, что это группа медленно ...
ajreal

Первый запрос выполняется за миллисекунды (один группируется и фильтруется с помощью HAVING). Это только в сочетании с другим запросом, который делает все медленно (это занимает минуты).
Quano

@diEcho, я хочу найти дубликаты, проверить их и удалить некоторые вручную.
Quano

Ответы:


112

Перепишите запрос в это

SELECT st1.*, st2.relevant_field FROM sometable st1
INNER JOIN sometable st2 ON (st1.relevant_field = st2.relevant_field)
GROUP BY st1.id  /* list a unique sometable field here*/
HAVING COUNT(*) > 1

Я думаю, что st2.relevant_fieldдолжно быть в выборе, потому что в противном случае havingпредложение выдаст ошибку, но я не уверен на 100%

Никогда не используйте INс подзапросом; это общеизвестно медленно.
Только когда-либо использовать INс фиксированным списком значений.

Больше советов

  1. Если вы хотите быстрее выполнять запросы, не SELECT *выбирайте только те поля, которые вам действительно нужны.
  2. Убедитесь, что у вас есть индекс relevant_fieldдля ускорения равного соединения.
  3. Обязательно group byна первичном ключе.
  4. Если вы находитесь на InnoDB и выбираете только индексированные поля (и все не слишком сложно), MySQL разрешит ваш запрос, используя только индексы, что ускорит процесс.

Общее решение для 90% ваших IN (select запросов

Используйте этот код

SELECT * FROM sometable a WHERE EXISTS (
  SELECT 1 FROM sometable b
  WHERE a.relevant_field = b.relevant_field
  GROUP BY b.relevant_field
  HAVING count(*) > 1) 

1
Вы также можете написать это с HAVING COUNT(*) > 1. Обычно это быстрее в MySQL.
ypercubeᵀᴹ

@ypercube, сделано для нижнего запроса, я думаю, что для верхнего запроса это изменит результат.
Йохан

@Johan: Поскольку st2.relevant_fieldнет NULL(он уже включен в ONпредложение), он не изменит результат.
ypercubeᵀᴹ

@ypercube, так что вы можете изменить счет (в поле) на счет (*), если вы уверены, afieldчто никогда не будет null, понял. Спасибо
Йохан

1
@quano, да она перечисляет все дубликаты , потому что group byна st1.id, а не на st1.relevant_field.
Йохан

110

Подзапрос выполняется для каждой строки, потому что это коррелированный запрос. Можно сделать коррелированный запрос в некоррелированный запрос, выбрав все из подзапроса, например так:

SELECT * FROM
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
) AS subquery

Окончательный запрос будет выглядеть так:

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT * FROM
    (
        SELECT relevant_field
        FROM some_table
        GROUP BY relevant_field
        HAVING COUNT(*) > 1
    ) AS subquery
)

3
Это сработало на удивление хорошо для меня. У меня был еще один IN (подзапрос) в IN (подзапрос), и он занимал более 10 минут, так долго, что я гуглил, пока ждал. Завершение каждого подзапроса в SELECT * FROM (), как вы предложили, уменьшило его до 2 секунд!
Лиам

СПАСИБО, я пытаюсь найти хороший способ сделать это в течение пары часов. Это сработало отлично. Хотел бы я дать вам больше голосов! Это определенно должно быть ответом.
thaspius

Работает отлично. Запрос, для выполнения которого потребовалось ~ 50 сек, теперь мгновенный. Хотелось бы, чтобы я проголосовал больше. Иногда вы не можете использовать объединения, так что это правильный ответ.
Симон

Интересно, почему оптимизатор считает запросы с объединениями взаимосвязанными ... В любом случае, этот трюк сработал как по волшебству
Брайан Лейшман

2
Не могли бы вы объяснить, что делает этот коррелированный подзапрос? Я понимаю, что подзапрос становится коррелированным, когда он использует значение, которое зависит от внешнего запроса. Но в этом примере я не вижу никаких взаимозависимостей. Это даст одинаковый результат для каждой строки, возвращаемой внешним запросом. У меня есть аналогичный пример, реализованный на MariaDB, и я не вижу никакого снижения производительности (пока), поэтому я хотел бы ясно видеть, когда эта SELECT *упаковка необходима.
sbnc.eu

6

Я подозревал что-то вроде этого, что подзапрос выполняется для каждой строки.
Quano

Некоторые версии MySQL даже не используют индекс в IN. Я добавил еще одну ссылку.
edze

1
MySQL 6 еще не стабилен, я бы не рекомендовал это для производства!
Йохан

1
Я бы не рекомендовал это. Но здесь объясняется, как это работает внутри (4.1 / 5.x -> 6). Это демонстрирует некоторые подводные камни текущих версий.
edze

5
SELECT st1.*
FROM some_table st1
inner join 
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
)st2 on st2.relevant_field = st1.relevant_field;

Я пробовал ваш запрос в одной из моих баз данных, а также попытался переписать его как объединение с подзапросом.

Это сработало намного быстрее, попробуйте!


Да, это, вероятно, создаст временную таблицу с результатами группы, поэтому она будет иметь ту же скорость, что и версия просмотра. Но планы запросов должны сказать правду.
ypercubeᵀᴹ

3

Попробуй это

SELECT t1.*
FROM 
 some_table t1,
  (SELECT relevant_field
  FROM some_table
  GROUP BY relevant_field
  HAVING COUNT (*) > 1) t2
WHERE
 t1.relevant_field = t2.relevant_field;

2

Я переформатировал ваш медленный SQL-запрос с www.prettysql.net

SELECT *
FROM some_table
WHERE
 relevant_field in
 (
  SELECT relevant_field
  FROM some_table
  GROUP BY relevant_field
  HAVING COUNT ( * ) > 1
 );

При использовании таблицы как в запросе, так и в подзапросе вы всегда должны использовать псевдоним обоих, например так:

SELECT *
FROM some_table as t1
WHERE
 t1.relevant_field in
 (
  SELECT t2.relevant_field
  FROM some_table as t2
  GROUP BY t2.relevant_field
  HAVING COUNT ( t2.relevant_field ) > 1
 );

Это помогает?


1
Это, к сожалению, не помогает. Это выполняется так же медленно.
Quano

Я обновил свой ответ, вы можете попробовать еще раз? Даже если группа медленно, она должна быть выполнена только один раз ...
plang

В прошлый раз я случайно убил живой сервер MySQL, поэтому боюсь, что не могу попробовать это прямо сейчас. Я должен настроить тестовую базу данных позже. Но я не понимаю, почему это должно повлиять на запрос. Оператор HAVING должен применяться только к запросу, в котором он находится, не так ли? Я действительно не понимаю, почему «реальный» запрос должен влиять на подзапрос.
Quano

Я нашел это: xaprb.com/blog/2006/04/30/… . Я думаю, что это может быть решением. Постараюсь, когда у меня будет время.
Quano

2

Во-первых, вы можете найти повторяющиеся строки и найти количество строк, которое используется, сколько раз, и упорядочить их по числу, как это;

SELECT q.id,q.name,q.password,q.NID,(select count(*) from UserInfo k where k.NID= q.NID) as Count,
(
		CASE q.NID
		WHEN @curCode THEN
			@curRow := @curRow + 1
		ELSE
			@curRow := 1
		AND @curCode := q.NID
		END
	) AS No
FROM UserInfo q,
(
		SELECT
			@curRow := 1,
			@curCode := ''
	) rt
WHERE q.NID IN
(
    SELECT NID
    FROM UserInfo
    GROUP BY NID
    HAVING COUNT(*) > 1
) 

после этого создайте таблицу и вставьте в нее результат.

create table CopyTable 
SELECT q.id,q.name,q.password,q.NID,(select count(*) from UserInfo k where k.NID= q.NID) as Count,
(
		CASE q.NID
		WHEN @curCode THEN
			@curRow := @curRow + 1
		ELSE
			@curRow := 1
		AND @curCode := q.NID
		END
	) AS No
FROM UserInfo q,
(
		SELECT
			@curRow := 1,
			@curCode := ''
	) rt
WHERE q.NID IN
(
    SELECT NID
    FROM UserInfo
    GROUP BY NID
    HAVING COUNT(*) > 1
) 

Наконец, удалите дублирующие строки. Нет начала 0. За исключением первого номера каждой группы, удалите все дублирующие строки.

delete from  CopyTable where No!= 0;


1

иногда, когда объем данных увеличивается, mysql WHERE IN может быть довольно медленным из-за оптимизации запросов. Попробуйте использовать STRAIGHT_JOIN, чтобы сказать MySQL выполнить запрос как есть, например

SELECT STRAIGHT_JOIN table.field FROM table WHERE table.id IN (...)

но будьте осторожны: в большинстве случаев оптимизатор mysql работает довольно хорошо, поэтому я бы рекомендовал использовать его только при возникновении подобных проблем


0

Это похоже на мой случай, когда у меня есть таблица с именем tabel_buku_besar. Что мне нужно

  1. Ищете записи, которые есть account_code='101.100'в tabel_buku_besarкоторых есть, companyarea='20000'а также имеют IDRкакcurrency

  2. Мне нужно получить все записи, у tabel_buku_besarкоторых есть account_code такой же, как на шаге 1, но transaction_numberна шаге 1 результат

во время использования select ... from...where....transaction_number in (select transaction_number from ....)мой запрос выполняется очень медленно и иногда приводит к истечению времени ожидания запроса или делает мое приложение не отвечающим ...

Я пробую эту комбинацию и результат ... неплохо ...

`select DATE_FORMAT(L.TANGGAL_INPUT,'%d-%m-%y') AS TANGGAL,
      L.TRANSACTION_NUMBER AS VOUCHER,
      L.ACCOUNT_CODE,
      C.DESCRIPTION,
      L.DEBET,
      L.KREDIT 
 from (select * from tabel_buku_besar A
                where A.COMPANYAREA='$COMPANYAREA'
                      AND A.CURRENCY='$Currency'
                      AND A.ACCOUNT_CODE!='$ACCOUNT'
                      AND (A.TANGGAL_INPUT BETWEEN STR_TO_DATE('$StartDate','%d/%m/%Y') AND STR_TO_DATE('$EndDate','%d/%m/%Y'))) L 
INNER JOIN (select * from tabel_buku_besar A
                     where A.COMPANYAREA='$COMPANYAREA'
                           AND A.CURRENCY='$Currency'
                           AND A.ACCOUNT_CODE='$ACCOUNT'
                           AND (A.TANGGAL_INPUT BETWEEN STR_TO_DATE('$StartDate','%d/%m/%Y') AND STR_TO_DATE('$EndDate','%d/%m/%Y'))) R ON R.TRANSACTION_NUMBER=L.TRANSACTION_NUMBER AND R.COMPANYAREA=L.COMPANYAREA 
LEFT OUTER JOIN master_account C ON C.ACCOUNT_CODE=L.ACCOUNT_CODE AND C.COMPANYAREA=L.COMPANYAREA 
ORDER BY L.TANGGAL_INPUT,L.TRANSACTION_NUMBER`

0

Я считаю, что это наиболее эффективно для обнаружения, если значение существует, логику можно легко перевернуть, чтобы найти, если значение не существует (то есть IS NULL);

SELECT * FROM primary_table st1
LEFT JOIN comparision_table st2 ON (st1.relevant_field = st2.relevant_field)
WHERE st2.primaryKey IS NOT NULL

* Замените релевантное поле именем значения, которое вы хотите проверить, существует в вашей таблице.

* Замените primaryKey именем столбца первичного ключа в таблице сравнения.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.