MySQL: Optimieren Sie UNION mit "ORDER BY" in inneren Abfragen


9

Ich habe gerade ein Protokollierungssystem eingerichtet, das aus mehreren Tabellen mit demselben Layout besteht.

Für jede Datenquelle gibt es eine Tabelle.

Für den Log Viewer möchte ich

  • UNION alle Protokolltabellen ,
  • filtern Sie sie nach Konto ,
  • Fügen Sie eine Pseudospalte zur Identifizierung der Quelle hinzu.
  • sortiere sie nach Zeit ,
  • und begrenzen sie für die Paginierung .

Alle Tabellen enthalten ein Feld mit dem Namen zeitpunktindizierte Datums- / Uhrzeitspalte.

Mein erster Versuch war:

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

Der Optimierer kann die Indizes hier nicht verwenden, da alle Zeilen aus beiden Tabellen von den Unterabfragen zurückgegeben und nach dem sortiert werden UNION.

Meine Problemumgehung war folgende:

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

Ich hatte erwartet, dass die Abfrage-Engine die Indizes hier verwenden würde, da beide Unterabfragen bereits vor dem sortiert und begrenzt werden sollten UNION, wodurch die Zeilen zusammengeführt und sortiert werden.

Ich dachte wirklich, das wäre es, aber EXPLAINwenn ich die Abfrage ausführe, werden die Unterabfragen immer noch in beiden Tabellen durchsucht.

EXPLAINingDie Unterabfragen selbst zeigen mir die gewünschte Optimierung, aber UNIONingzusammen nicht.

Habe ich etwas verpasst?

Ich weiß, dass ORDER BYKlauseln in UNIONUnterabfragen ohne a ignoriert werden LIMIT, aber es gibt eine Grenze.

Bearbeiten:
Eigentlich wird es wahrscheinlich auch Abfragen ohne dieaccount_idBedingung geben.

Die Tabellen existieren bereits und sind mit Daten gefüllt. Abhängig von der Quelle kann es zu Änderungen im Layout kommen, daher möchte ich sie geteilt halten. Darüber hinaus verwenden die Protokollierungsclients aus einem bestimmten Grund unterschiedliche Anmeldeinformationen.

Ich muss eine Art Schicht zwischen den Protokolllesern und den tatsächlichen Tabellen halten.

Hier sind die Ausführungspläne für die gesamte Abfrage und die erste Unterabfrage sowie das Tabellenlayout im Detail:

https://gist.github.com/ca8fc1093cd95b1c6fc0


1
Der beste Index dafür wäre die Verbindung (account_id, zeitpunkt). Haben Sie einen solchen Index? Das zweitbeste wäre (glaube ich) die Single (zeitpunkt)- aber die Effizienz, wenn dies verwendet wird, hängt davon ab, wie oft Zeilen mit account_id=730erscheinen.
Ypercubeᵀᴹ

2
Und warum UNION DISTINCT? Es ist nicht erforderlich, dort eine Sortierung und Unterscheidung zu erzwingen, da die Ergebnisse aufgrund der zusätzlichen Identifikationsspalte je nach Unterabfrage unterschiedlich sind. Verwenden Sie UNION ALL.
Ypercubeᵀᴹ

1
Zusätzlich zu dem Vorschlag von @ ypercube habe ich eine Frage: Wäre es nicht besser, alle diese Protokolle mit der sourceSpalte in derselben Tabelle zu haben ? Auf diese Weise können Sie UNIONs vermeiden und Indizes für alle Ihre Daten verwenden.
Dekso

1
@ypercube Tatsächlich wird es wahrscheinlich auch Abfragen ohne die Bedingung account_id geben . Das DISTINCT- Flag ist ein Relikt früherer Versuche und tatsächlich nutzlos, da die Ergebnisse immer unterschiedlich sind und DISTINCT das dafualt-Verhalten ist. Die Tabellen existieren bereits und sind mit Daten gefüllt. Auf jeden Fall kann es je nach Quelle zu Änderungen im Layout kommen, daher möchte ich sie geteilt halten. Darüber hinaus verwenden die Protokollierungsclients aus einem bestimmten Grund unterschiedliche Anmeldeinformationen. Ich muss eine Art Schicht zwischen den Protokolllesern und den tatsächlichen Tabellen halten.
Lukas

OK, aber prüfen Sie, ob eine Änderung zu einem UNION ALLanderen Ausführungsplan führt.
Ypercubeᵀᴹ

Antworten:


7

Können Sie diese Version aus Neugier ausprobieren? Es kann den Optimierer dazu verleiten, dieselben Indizes zu verwenden, die die Unterabfragen separat verwenden würden:

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;

Ich denke immer noch, dass der beste Index, den Sie haben könnten, die Verbindung ist (account_id, zeitpunkt). Es würde die 10 Reihen schnell ergeben, und es wären keine Tricks nötig.


Ihre Änderung brachte die gewünschten Ergebnisse. Vielen Dank! Nur als Randnotiz: Ich bin mir nicht sicher, welcher Index besser sein wird. Ich könnte sogar beides gebrauchen. Ich muss überprüfen, wie sich die Anzahl der Benutzer und der log entries / userWille skalieren lassen.
Lukas

Wenn Sie Abfragen mit und ohne Abfragen benötigen account_id=?, behalten Sie beide bei.
Ypercubeᵀᴹ

@ypercube, +1 das ist sehr klug und hat auch in meiner (ähnlichen) Situation funktioniert! Können Sie erklären, warum das Umschließen der vereinigten Abfragen in einen Dummy SELECT * FROMMySQL dazu verleitet , die Indizes zu verwenden?
Dkamins

@dkamins: Der MySQL-Optimierer ist nicht sehr clever. Wenn es eine abgeleitete Tabelle wie hier gibt (SELECT ...) AS a, versucht er normalerweise , die abgeleitete Tabelle getrennt von den anderen abgeleiteten Tabellen und dann der gesamten Abfrage auszuwerten und zu optimieren.
Ypercubeᵀᴹ

@Lukas, Da Sie sicherstellen müssen, dass der Index verwendet wird, erhalten Sie mit / add force indexeine bessere Lösung.
Pacerier
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.