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;