Почему условие IN будет медленнее, чем "=" в sql? - PullRequest
28 голосов
/ 05 августа 2010

Проверьте вопрос Этот запрос SELECT занимает 180 секунд, чтобы завершить (проверьте комментарии к самому вопросу).
IN можно сравнивать только с одним значением, но разница во времени огромна.
Почему это так?

Ответы [ 4 ]

47 голосов
/ 05 августа 2010

Краткое описание: Это известная проблема в MySQL, исправленная в MySQL 5.6.x.Проблема связана с отсутствующей оптимизацией, когда подзапрос, использующий IN, неправильно идентифицируется как зависимый подзапрос вместо независимого подзапроса.


Когда вы запускаете EXPLAIN для исходного запроса, он возвращает следующее:

1  'PRIMARY'             'question_law_version'  'ALL'  ''  ''  ''  ''  10148  'Using where'
2  'DEPENDENT SUBQUERY'  'question_law_version'  'ALL'  ''  ''  ''  ''  10148  'Using where'
3  'DEPENDENT SUBQUERY'  'question_law'          'ALL'  ''  ''  ''  ''  10040  'Using where'

Когда вы изменяете IN на =, вы получаете это:

1  'PRIMARY'   'question_law_version'  'ALL'  ''  ''  ''  ''  10148  'Using where'
2  'SUBQUERY'  'question_law_version'  'ALL'  ''  ''  ''  ''  10148  'Using where'
3  'SUBQUERY'  'question_law'          'ALL'  ''  ''  ''  ''  10040  'Using where'

Каждый зависимый подзапрос запускается один раз для каждой строки в запросе, в котором он содержится, тогда как подзапрос выполняется толькоодин раз.MySQL иногда может оптимизировать зависимые подзапросы, когда есть условие, которое можно преобразовать в соединение, но здесь это не так.

Теперь это, конечно, оставляет вопрос о том, почему MySQL считает, что версия IN должна бытьзависимый подзапрос.Я сделал упрощенную версию запроса, чтобы помочь исследовать это.Я создал две таблицы 'foo' и 'bar', где первая содержит только столбец id, а вторая содержит и id, и идентификатор foo (хотя я не создала ограничение внешнего ключа).Затем я заполнил обе таблицы 1000 строками:

CREATE TABLE foo (id INT PRIMARY KEY NOT NULL);
CREATE TABLE bar (id INT PRIMARY KEY, foo_id INT NOT NULL);

-- populate tables with 1000 rows in each

SELECT id
FROM foo
WHERE id IN
(
    SELECT MAX(foo_id)
    FROM bar
);

Этот упрощенный запрос имеет ту же проблему, что и раньше - внутренний выбор обрабатывается как зависимый подзапрос, и оптимизация не выполняется, в результате чего внутренний запрос выполняетсяодин раз за рядВыполнение запроса занимает почти одну секунду.Повторное изменение IN на = позволяет выполнить запрос почти мгновенно.

Код, который я использовал для заполнения таблиц, приведен ниже, на случай, если кто-нибудь захочет воспроизвести результаты.

CREATE TABLE filler (
        id INT NOT NULL PRIMARY KEY AUTO_INCREMENT
) ENGINE=Memory;

DELIMITER $$

CREATE PROCEDURE prc_filler(cnt INT)
BEGIN
        DECLARE _cnt INT;
        SET _cnt = 1;
        WHILE _cnt <= cnt DO
                INSERT
                INTO    filler
                SELECT  _cnt;
                SET _cnt = _cnt + 1;
        END WHILE;
END
$$

DELIMITER ;

CALL prc_filler(1000);

INSERT foo SELECT id FROM filler;
INSERT bar SELECT id, id FROM filler;
1 голос
/ 05 августа 2010

Речь идет о внутренних запросах a.k.a подзапросах к объединениям, а не о IN vs =, и причины этого объясняются в этом посте. Предполагается, что в версии MySQL 5.4 появился улучшенный оптимизатор, который может переписать некоторые подзапросы в более эффективную форму.

Худшее, что вы можете сделать, это использовать так называемый коррелированный подзапрос. http://dev.mysql.com/doc/refman/5.1/en/correlated-subqueries.html

0 голосов
/ 04 мая 2013

Интересно, но проблему также можно решить с помощью подготовленных операторов (не уверен, подходит ли он всем), например:

mysql> EXPLAIN SELECT * FROM words WHERE word IN (SELECT word FROM phrase_words);
+----+--------------------+--------------+...
| id | select_type        | table        |...
+----+--------------------+--------------+...
|  1 | PRIMARY            | words        |...
|  2 | DEPENDENT SUBQUERY | phrase_words |...
+----+--------------------+--------------+...
mysql> EXPLAIN SELECT * FROM words WHERE word IN ('twist','rollers');
+----+-------------+-------+...
| id | select_type | table |...
+----+-------------+-------+...
|  1 | SIMPLE      | words |...
+----+-------------+-------+...

Так что просто подготовьте оператор в хранимой процедуре, а затемвыполнить его.Вот идея:

SET @words = (SELECT GROUP_CONCAT(word SEPARATOR '\',\'') FROM phrase_words);
SET @words = CONCAT("'", @words, "'");
SET @query = CONCAT("SELECT * FROM words WHERE word IN (", @words, ");";
PREPARE q FROM @query;
EXECUTE q;
0 голосов
/ 05 августа 2010

SQL-оптимизаторы не всегда делают то, что от них ожидают.Я не уверен, что есть лучший ответ, чем это.Вот почему вы должны изучить выходные данные EXPLAIN PLAN и профилировать свои запросы, чтобы узнать, на что тратится время.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...