MySQL: ottimizza UNION con "ORDER BY" nelle query interne


9

Ho appena impostato un sistema di registrazione composto da più tabelle con lo stesso layout.

C'è una tabella per ogni origine dati.

Per il visualizzatore di log, voglio farlo

  • UNIONE tutte le tabelle di registro ,
  • filtrali per account ,
  • aggiungere una pseudo colonna per l'identificazione della fonte,
  • ordinali per tempo ,
  • e limitarli per l'impaginazione .

Tutte le tabelle contengono un campo chiamato zeitpunktche è una colonna data / ora indicizzata.

Il mio primo tentativo è stato:

(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;

L'ottimizzatore non può utilizzare gli indici qui perché tutte le righe di entrambe le tabelle vengono restituite dalle sottoquery e ordinate dopo il UNION.

La mia soluzione alternativa era la seguente:

(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;

Mi aspettavo che il motore di query avrebbe usato gli indici qui poiché entrambe le sottoquery dovrebbero essere ordinate e limitate già prima di UNION, che quindi unisce e ordina le righe.

Pensavo davvero che sarebbe successo, ma l'esecuzione EXPLAINsulla query mi dice che le sottoquery continuano a cercare entrambe le tabelle.

EXPLAINingle subquery stesse mi mostrano l'ottimizzazione desiderata ma UNIONingloro insieme no.

Ho dimenticato qualcosa?

So che le ORDER BYclausole all'interno delle UNIONsottoquery vengono ignorate senza un LIMIT, ma esiste un limite.

Modifica: in
realtà, probabilmente ci saranno anche query senza laaccount_idcondizione.

Le tabelle esistono già e sono piene di dati. Potrebbero esserci cambiamenti nel layout a seconda della fonte, quindi voglio tenerli divisi. Inoltre, i client di registrazione utilizzano credenziali diverse per un motivo.

Devo mantenere una sorta di livello tra i lettori di log e le tabelle effettive.

Ecco i piani di esecuzione per l'intera query e la prima sottoquery, nonché il layout della tabella in dettaglio:

https://gist.github.com/ca8fc1093cd95b1c6fc0


1
L'indice migliore per questo sarebbe il composto (account_id, zeitpunkt). Hai un tale indice? Il secondo migliore sarebbe (penso) il singolo (zeitpunkt)- ma l'efficienza se utilizzata dipende dalla frequenza con cui account_id=730appaiono le righe .
ypercubeᵀᴹ

2
E perchè UNION DISTINCT? Non è necessario forzare una sorta e distinguere lì, poiché i risultati saranno diversi tra le sottoquery, a causa della colonna di identificazione aggiuntiva. Usa UNION ALL.
ypercubeᵀᴹ

1
Oltre al suggerimento di @ ypercube, ho una domanda: non sarebbe meglio avere tutti quei registri nella stessa tabella, con l'aggiunta della sourcecolonna? In questo modo potresti evitare di UNIONusare gli indici su tutti i tuoi dati.
dezso

1
@ypercube In realtà, probabilmente ci saranno anche query senza la condizione account_id . Il flag DISTINCT è un relitto di un tentativo precedente ed è in realtà inutile perché i risultati saranno sempre diversi e perché DISTINCT è il comportamento dafualt. Le tabelle esistono già e sono piene di dati. Ad ogni modo, potrebbero esserci dei cambiamenti nel layout a seconda della fonte, quindi voglio tenerli divisi. Inoltre, i client di registrazione utilizzano credenziali diverse per un motivo. Devo mantenere una sorta di livello tra i lettori di log e le tabelle effettive.
Lukas,

OK, ma controlla se la modifica porta a un UNION ALLpiano di esecuzione diverso.
ypercubeᵀᴹ

Risposte:


8

Solo per curiosità, puoi provare questa versione? Può ingannare l'ottimizzatore per utilizzare gli stessi indici che le sottoquery utilizzerebbero separatamente:

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;

Penso ancora che il miglior indice che potresti avere sia il composto (account_id, zeitpunkt). Produrrebbe velocemente le 10 file e non sarebbe necessario alcun trucco.


La tua modifica è risultata per portare i risultati desiderati. Grazie! Proprio come una nota a margine: ormai non sono sicuro quale indice sarà migliore. Potrei persino usare entrambi. Dovrò controllare come log entries / userridimensiona il numero di utenti e la volontà.
Lukas,

Se hai bisogno di query con e query senza account_id=?, tieni entrambe.
ypercubeᵀᴹ

@ypercube, +1 questo è molto intelligente e ha funzionato anche nella mia (simile) situazione! Puoi spiegare perché avvolgere le query unite in un manichino SELECT * FROMinganna MySQL nell'uso degli indici?
dkamins

@dkamins: l'ottimizzatore MySQL non è molto intelligente, di solito quando esiste una tabella derivata come qui (SELECT ...) AS a, cerca di valutare e ottimizzare la tabella derivata separatamente dalle altre tabelle derivate e quindi l'intera query.
ypercubeᵀᴹ

@Lukas, In realtà, poiché è necessario assicurarsi che l'indice venga utilizzato, l'utilizzo / aggiunta force indexoffre una soluzione migliore.
Pacerier,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.