MySQL: Otimize UNION com "ORDER BY" em consultas internas

9

Acabei de configurar um sistema de registro que consiste em várias tabelas com o mesmo layout.

Há uma tabela para cada fonte de dados.

Para o visualizador de logs, quero

  • UNION todas as tabelas de log ,
  • filtrá-los por conta ,
  • adicione uma pseudo-coluna para identificação da fonte,
  • classificá-los por tempo ,
  • e limite-os para paginação .

Todas as tabelas contêm um campo chamado zeitpunktque é uma coluna de data / hora indexada.

Minha primeira tentativa foi:

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730)

UNION

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730)

ORDER BY zeit DESC LIMIT 10;

O otimizador não pode usar os índices aqui porque todas as linhas de ambas as tabelas são retornadas pelas subconsultas e classificadas após o UNION.

Minha solução alternativa foi a seguinte:

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)

UNION

(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)

ORDER BY zeit DESC LIMIT 10;

Eu esperava que o mecanismo de consulta usasse os índices aqui, já que as duas subconsultas já deveriam ser classificadas e limitadas antes da UNION, que depois mescla e classifica as linhas.

Eu realmente pensei que seria isso, mas a execução EXPLAINda consulta me diz que as subconsultas ainda pesquisam as duas tabelas.

EXPLAINingas próprias subconsultas me mostram a otimização desejada, mas UNIONingelas juntas não.

Perdi alguma coisa?

Eu sei que ORDER BYcláusulas dentro de UNIONsubconsultas são ignoradas sem a LIMIT, mas há um limite.

Edit:
Na verdade, provavelmente também haverá consultas sem aaccount_idcondição.

As tabelas já existem e são preenchidas com dados. Pode haver alterações no layout, dependendo da fonte, então eu quero mantê-las divididas. Além disso, os clientes de log usam credenciais diferentes por um motivo.

Eu tenho que manter um tipo de camada entre os leitores de log e as tabelas reais.

Aqui estão os planos de execução para toda a consulta e a primeira subconsulta, bem como o layout da tabela em detalhes:

https://gist.github.com/ca8fc1093cd95b1c6fc0

Lukas
fonte
11
O melhor índice para isso seria o composto (account_id, zeitpunkt). Você tem esse índice? O segundo melhor seria (acho) o single (zeitpunkt)- mas a eficiência, se usada, depende da frequência com que as linhas account_id=730aparecem.
usar o seguinte comando
2
E por que UNION DISTINCT? Não há necessidade de forçar uma classificação e distinção lá, pois os resultados serão diferentes nas subconsultas, devido à coluna de identificação extra. Use UNION ALL.
usar o seguinte comando
11
Além da sugestão de @ ypercube, tenho uma pergunta: não seria melhor ter todos esses logs na mesma tabela, com a adição da sourcecoluna? Dessa forma, você pode evitar se UNIONusar índices em todos os seus dados.
dezso '
11
@ypercube Na verdade, provavelmente também haverá consultas sem a condição account_id . O sinalizador DISTINCT é um relict de tentativas anteriores e é realmente inútil porque os resultados sempre diferem e porque DISTINCT é o comportamento dafualt. As tabelas já existem e são preenchidas com dados. De qualquer forma, pode haver alterações no layout, dependendo da fonte, então eu quero mantê-las divididas. Além disso, os clientes de log usam credenciais diferentes por um motivo. Eu tenho que manter um tipo de camada entre os leitores de log e as tabelas reais.
Lukas
OK, mas verifique se a alteração para UNION ALLproduz um plano de execução diferente.
usar o seguinte comando

Respostas:

8

Por curiosidade, você pode experimentar esta versão? Pode ser um truque para o otimizador usar os mesmos índices que as subconsultas usariam separadamente:

SELECT *
FROM
(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt AS zeit,
 'hp' AS source FROM is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10) 
    AS a

UNION ALL

SELECT *
FROM
(SELECT l.id, l.account_id, l.vnum, l.count, l.preis, l.zeitpunkt,
 'ig' AS source FROM ig_is_log AS l WHERE l.account_id = 730
 ORDER BY l.zeitpunkt DESC LIMIT 10)
    AS b

ORDER BY zeit DESC LIMIT 10;

Eu ainda acho que o melhor índice que você poderia ter é o composto (account_id, zeitpunkt). Produziria as 10 linhas rapidamente, e nenhum truque seria necessário.

ypercubeᵀᴹ
fonte
Sua modificação acabou trazendo os resultados desejados. Obrigado! Apenas como uma observação lateral: agora não tenho certeza de qual índice será melhor. Eu poderia até usar os dois. Vou ter que verificar como o número de usuários e a log entries / userescala serão dimensionados.
Lukas
Se você precisar de consultas com e sem account_id=?, mantenha as duas.
usar o seguinte código
@ypercube, +1 isso é muito inteligente e funcionou na minha situação (semelhante) também! Você pode explicar por que agrupar as consultas unidas em um manequim SELECT * FROMengana o MySQL para usar os índices?
dkamins
@dkamins: O otimizador do MySQL não é muito inteligente, geralmente quando existe uma tabela derivada como a seguinte (SELECT ...) AS a, ele tenta avaliar e otimizar a tabela derivada separadamente das outras tabelas derivadas e depois de toda a consulta.
precisa saber é o seguinte
@ Lucas, Na verdade, como você precisa garantir que o índice seja usado, usar / adicionar force indexfornecerá uma solução melhor.
Pacerier 5/05