Após uma migração do MySQL 5.7 para o MySQL 8.0.34, temos um comportamento muito estranho com uma consulta quando semijoin
está on
no otimizador_switch.
1. A consulta problemática:
SELECT COUNT(s0_.id)
FROM stores_shoppers s0_
WHERE s0_.id NOT IN (
SELECT s4_.id
FROM emails_history i5_
INNER JOIN stores_shoppers s4_ ON i5_.shopper_id = s4_.id
WHERE i5_.rule_id IN (1517676 , 1517677)
)
Neste caso este é o plano de execução:
eu ia | selecione o tipo | mesa | partições | tipo | chaves_possíveis | chave | key_len | referência | linhas | filtrado | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLES | s0_ | NULO | índice | NULO | lojas_shoppers_store_id_idx | 4 | NULO | 361627391 | 100,00 | Usando índice |
1 | SIMPLES | NULO | eq_ref | <auto_distinct_key> | <auto_distinct_key> | 5 | remarkety_prod.s0_.id | 1 | 100,00 | Usando onde; Não existe | |
2 | MATERIALIZADO | i5_ | datas_inválidas,p2022m03,... | TODOS | emails_history_rule_id_idx | NULO | NULO | NULO | 7526556120 | 100,00 | Usando onde |
2 | MATERIALIZADO | s4_ | NULO | eq_ref | PRIMÁRIO | PRIMÁRIO | 4 | remarkety_prod.i5_.shopper_id | 1 | 100,00 | Usando índice |
O problema é que o tipo i5_JOIN é ALL, nenhum índice é usado no JOIN.
2. Com apenas um valor na cláusula IN:
Se a IN
cláusula na consulta incluir apenas um valor:
SELECT COUNT(s0_.id)
FROM stores_shoppers s0_
WHERE s0_.id NOT IN (
SELECT s4_.id
FROM emails_history i5_
INNER JOIN stores_shoppers s4_ ON i5_.shopper_id = s4_.id
WHERE i5_.rule_id IN (1517676) -- only one value
)
O plano de execução passa a ser:
eu ia | selecione o tipo | mesa | partições | tipo | chaves_possíveis | chave | key_len | referência | linhas | filtrado | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLES | s0_ | NULO | índice | NULO | lojas_shoppers_store_id_idx | 4 | NULO | 361632497 | 100,00 | Usando índice |
1 | SIMPLES | NULO | eq_ref | <auto_distinct_key> | <auto_distinct_key> | 5 | remarkety_prod.s0_.id | 1 | 100,00 | Usando onde; Não existe | |
2 | MATERIALIZADO | i5_ | datas_inválidas,p2022m03,... | referência | emails_history_rule_id_idx | emails_history_rule_id_idx | 5 | const | 56944 | 100,00 | NULO |
2 | MATERIALIZADO | s4_ | NULO | eq_ref | PRIMÁRIO | PRIMÁRIO | 4 | remarkety_prod.i5_.shopper_id | 1 | 100,00 | Usando índice |
Neste caso, o JOIN usa um índice mas o plano de execução ainda é diferente do plano de execução que temos no MySQL 5.7
3. Usando i5_.shopper_id em vez de s4_.id (que deveria ser idêntico)
SELECT COUNT(s0_.id)
FROM stores_shoppers s0_
WHERE s0_.id NOT IN (
SELECT i5_.shopper_id -- using i5_.shopper_id instead of s4_.id
FROM emails_history i5_
INNER JOIN stores_shoppers s4_ ON i5_.shopper_id = s4_.id
WHERE i5_.rule_id IN (1517676 , 1517677)
eu ia | selecione o tipo | mesa | partições | tipo | chaves_possíveis | chave | key_len | referência | linhas | filtrado | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMÁRIO | s0_ | NULO | índice | NULO | lojas_shoppers_store_id_idx | 4 | NULO | 361632516 | 100,00 | Usando onde; Usando índice |
2 | SUBCONSULTA | i5_ | datas_inválidas,p2022m03,... | faixa | emails_history_rule_id_idx | emails_history_rule_id_idx | 5 | NULO | 56945 | 100,00 | Usando condição de índice; Usando onde |
2 | SUBCONSULTA | s4_ | NULO | eq_ref | PRIMÁRIO | PRIMÁRIO | 4 | remarkety_prod.i5_.shopper_id | 1 | 100,00 | Usando índice |
Neste caso, o plano de execução é o mesmo que tínhamos no MySQL 5.7
4. Com semijoin=off
SET optimizer_switch = 'semijoin=off';
SELECT COUNT(s0_.id)
FROM stores_shoppers s0_
WHERE s0_.id NOT IN (
SELECT s4_.id
FROM emails_history i5_
INNER JOIN stores_shoppers s4_ ON i5_.shopper_id = s4_.id
WHERE i5_.rule_id IN (1517676 , 1517677)
)
eu ia | selecione o tipo | mesa | partições | tipo | chaves_possíveis | chave | key_len | referência | linhas | filtrado | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMÁRIO | s0_ | NULO | índice | NULO | lojas_shoppers_store_id_idx | 4 | NULO | 361632550 | 100,00 | Usando onde; Usando índice |
2 | SUBCONSULTA | i5_ | datas_inválidas,p2022m03,... | faixa | emails_history_rule_id_idx | emails_history_rule_id_idx | 5 | NULO | 56945 | 100,00 | Usando condição de índice; Usando onde |
2 | SUBCONSULTA | s4_ | NULO | eq_ref | PRIMÁRIO | PRIMÁRIO | 4 | remarkety_prod.i5_.shopper_id | 1 | 100,00 | Usando índice |
O mesmo plano de execução de antes.
Questões:
Alguém pode me explicar o que está acontecendo?
Isso é um bug no otimizador?
Por que a junção não usa índice na primeira consulta?
Problema com otimizador_switch – Envie um relatório de bug em bugs.mysql.com
Recomendo adicionar estes índices:
Algumas dicas gerais:
COUNT(x)
testesx
para serNOT NULL
. Você provavelmente queriaCOUNT(*)
.NOT IN ( SELECT ... )
há muito tempo é uma construção mal otimizada. 5,7 melhorou; 8.0 tentou melhorar ainda mais, mas talvez não neste caso.IN(1517676)
é otimizado como= 1517676
, masIN (1517676 , 1517677)
também não é otimizado. Às vezes, um multiitemIN
é melhor otimizado comUNION
.EXISTS(SELECT ...)
Pensamentos sobre desempenho
Não entendo o objetivo da consulta, mas aqui estão algumas coisas que eu tentaria fazer:
stores_shoppers
duas vezes.NOT EXISTS
eLEFT JOIN ... IS NULL
( SELECT ... rule_id = 123 ) UNION ALL ( SELECT ... rule_id = 123 )