MySQL: Optimiser UNION avec "ORDER BY" dans les requêtes internes

9

Je viens de mettre en place un système d'enregistrement qui se compose de plusieurs tables avec la même disposition.

Il existe une table pour chaque source de données.

Pour la visionneuse de journaux, je veux

  • UNION toutes les tables de log ,
  • les filtrer par compte ,
  • ajouter une pseudo colonne pour l'identification de la source,
  • les trier par temps ,
  • et les limiter pour la pagination .

Toutes les tables contiennent un champ appelé zeitpunktqui est une colonne date / heure indexée.

Ma première tentative a été:

(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'optimiseur ne peut pas utiliser les index ici car toutes les lignes des deux tables sont renvoyées par les sous-requêtes et triées après le UNION.

Ma solution de contournement était la suivante:

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

Je m'attendais à ce que le moteur de requête utilise les index ici car les deux sous-requêtes devraient être triées et limitées déjà avant le UNION, qui fusionne et trie ensuite les lignes.

Je pensais vraiment que ce serait le cas, mais l'exécution EXPLAINde la requête me dit que les sous-requêtes recherchent toujours les deux tables.

EXPLAINingles sous-requêtes elles-mêmes me montrent l'optimisation souhaitée mais UNIONingelles ne le font pas ensemble.

Ai-je oublié quelque chose?

Je sais que les ORDER BYclauses à l'intérieur des UNIONsous-requêtes sont ignorées sans a LIMIT, mais il y a une limite.

Edit:
En fait, il y aura probablement aussi des requêtes sansaccount_idcondition.

Les tableaux existent déjà et sont remplis de données. Il peut y avoir des changements dans la mise en page selon la source, donc je veux les garder divisés. En outre, les clients de journalisation utilisent des informations d'identification différentes pour une raison.

Je dois garder une sorte de couche entre les lecteurs de journaux et les tables réelles.

Voici les plans d'exécution de la requête entière et de la première sous-requête ainsi que la disposition du tableau en détail:

https://gist.github.com/ca8fc1093cd95b1c6fc0

Lukas
la source
1
Le meilleur indice serait le composé (account_id, zeitpunkt). Avez-vous un tel indice? Le deuxième meilleur serait (je pense) le single (zeitpunkt)- mais l'efficacité si elle est utilisée dépend de la fréquence d' account_id=730apparition des lignes avec .
ypercubeᵀᴹ
2
Et pourquoi UNION DISTINCT? Il n'est pas nécessaire de forcer un tri et de les distinguer, car les résultats seront différents d'une sous-requête à l'autre en raison de la colonne d'identification supplémentaire. Utilisez UNION ALL.
ypercubeᵀᴹ
1
En plus de la suggestion de @ ypercube, j'ai une question: ne serait-il pas préférable d'avoir tous ces journaux dans le même tableau, avec l'ajout de la sourcecolonne? De cette façon, vous pouvez éviter les UNIONs et utiliser des index dans toutes vos données.
dezso
1
@ypercube En fait, il y aura probablement aussi des requêtes sans la condition account_id . Le drapeau DISTINCT est une relique des tentatives précédentes et est en fait inutile car les résultats seront toujours différents et parce que DISTINCT est le comportement dafualt. Les tableaux existent déjà et sont remplis de données. Quoi qu'il en soit, il peut y avoir des changements dans la mise en page selon la source, donc je veux les garder divisés. En outre, les clients de journalisation utilisent des informations d'identification différentes pour une raison. Je dois garder une sorte de couche entre les lecteurs de journaux et les tables réelles.
Lukas
OK, mais vérifiez si le changement UNION ALLdonne un plan d'exécution différent.
ypercubeᵀᴹ

Réponses:

8

Par curiosité, pouvez-vous essayer cette version? Il peut tromper l'optimiseur d'utiliser les mêmes indices que les sous-requêtes utiliseraient séparément:

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;

Je pense toujours que le meilleur indice que vous pourriez avoir est le composé (account_id, zeitpunkt). Cela donnerait les 10 lignes rapidement et aucune astuce ne serait nécessaire.

ypercubeᵀᴹ
la source
Votre modification s'est avérée apporter les résultats souhaités. Merci! Juste une remarque: à présent, je ne sais pas quel indice sera le meilleur. Je pourrais même utiliser les deux. Je vais devoir vérifier comment le nombre d'utilisateurs et la log entries / uservolonté évolueront.
Lukas
Si vous avez besoin de requêtes avec et sans account_id=?, conservez les deux.
ypercubeᵀᴹ
@ypercube, +1 c'est très intelligent et a fonctionné dans ma situation (similaire) aussi! Pouvez-vous expliquer pourquoi l'encapsulation des requêtes réunies dans un mannequin incite SELECT * FROMMySQL à utiliser les index?
dkamins
@dkamins: L'optimiseur MySQL n'est pas très intelligent, généralement quand il y a une table dérivée comme ici le (SELECT ...) AS a, il essaie d'évaluer et d'optimiser la table dérivée séparément des autres tables dérivées et ensuite de la requête entière.
ypercubeᵀᴹ
@Lukas, En fait, puisque vous devez vous assurer que l'index est utilisé, l'utilisation de / add force indexvous donnera une meilleure solution.
Pacerier