Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Por que uma condição IN seria mais lenta que =em sql?


Resumo:Este é um problema conhecido no MySQL e foi corrigido no MySQL 5.6.x. O problema é devido a uma otimização ausente quando uma subconsulta usando IN é identificada incorretamente como subconsulta dependente em vez de uma subconsulta independente.

Quando você executa EXPLAIN na consulta original, ele retorna isso:
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'

Quando você altera IN para = você consegue isso:
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'

Cada subconsulta dependente é executada uma vez por linha na consulta em que está contida, enquanto a subconsulta é executada apenas uma vez. O MySQL às vezes pode otimizar subconsultas dependentes quando há uma condição que pode ser convertida em uma junção, mas aqui não é o caso.

Agora, é claro, isso deixa a questão de por que o MySQL acredita que a versão IN precisa ser uma subconsulta dependente. Fiz uma versão simplificada da consulta para ajudar a investigar isso. Eu criei duas tabelas 'foo' e 'bar' onde a primeira contém apenas uma coluna id, e a última contém uma id e uma id foo (embora eu não tenha criado uma restrição de chave estrangeira). Então eu preenchi ambas as tabelas com 1000 linhas:
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
);

Essa consulta simplificada tem o mesmo problema de antes - a seleção interna é tratada como uma subconsulta dependente e nenhuma otimização é executada, fazendo com que a consulta interna seja executada uma vez por linha. A consulta leva quase um segundo para ser executada. Alterando o IN para = novamente permite que a consulta seja executada quase instantaneamente.

O código que usei para preencher as tabelas está abaixo, caso alguém queira reproduzir os resultados.
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;