2017-08-13 1 views
0

In unserer Anwendung versuchen wir, die bestmögliche Übereinstimmung für einen gegebenen Satz von Parametern zu finden. Wir haben diese Zeilen in verschiedene Qualitätsgruppen unterteilt, die mit einer Teilmenge des gesamten Parametersatzes übereinstimmen. Um diesen verschiedenen Gruppen zu entsprechen, haben wir mehrere Select-Abfragen, die wir abfragen, wenn kein Ergebnis gefunden wurde. Wir entschieden uns nun, sie mit UNION ALL mit LIMIT 1 zusammenzufügen.Erste vorhandene Zeile aus mehreren Abfragen

SET @size = 4, @price = 18, @category = 'NEW', @weight = 20, @origin = 'France'; 
(SELECT * FROM product_catalog WHERE quality = 'A1' AND size = @size AND price = @price AND category = @category AND weight = @weight AND origin = @origin LIMIT 1) 
UNION ALL 
(SELECT * FROM product_catalog WHERE quality = 'A2' AND size = @size AND price = @price AND category = @category AND origin = @origin LIMIT 1) 
UNION ALL 
(SELECT * FROM product_catalog WHERE quality = 'A3' AND price = @price AND category = @category AND weight = @weight AND origin = @origin LIMIT 1) 
UNION ALL 
(SELECT * FROM product_catalog WHERE quality = 'A4' AND price = @price AND category = @category AND origin = @origin LIMIT 1) 
UNION ALL 
... SOME MORE SELECTS ... 
LIMIT 1 

Jetzt läuft die Abfrage wie erwartet, es führt jedoch viel schlechter als unsere aktuelle Lösung. Ich denke, dass dies mit der Tatsache zu tun hat, dass MySQL die UNION-Anweisungen zuerst ausführt und dann realisiert, dass nur die erste zurückgegeben werden muss?

Haben Sie Vorschläge zur Beschleunigung der Abfrage? Denkst du, dass es möglich ist, die Abfrage in eine gespeicherte Prozedur umzuschreiben, die jede Abfrage nach einem Ergebnis prüft und zurückgibt, sobald sie gefunden wird? Wird dies die Abfrage beschleunigen?

+0

MySQL alle Teile evaluieren. Einige Gedanken jedoch: a) ohne eine Gesamtordnung von, Ihre letzte Grenze kann * ANY * unspezifizierte zufällige Zeile einer Union, so ist es nicht gleichbedeutend mit der ersten Abfrage und nur mit dem nächsten weiter, wenn Sie nichts gefunden. b) Wenn Sie Indizes für alle Kombinationen hinzufügen, sollte diese Abfrage in <0,2s ausgeführt werden. Nicht sicher, ob es das bereits tut und Sie brauchen es nur, um schneller zu sein, wenn Sie 1000 Mal pro Minute laufen, aber ansonsten sollten Sie zuerst Ihre Indizes überprüfen.c) 'or', eine' Reihenfolge nach Qualität' und nur ein Limit kann schneller als 'union' sein, abhängig von den Indizes. – Solarflare

+0

Vielen Dank für Ihre Antwort. Ich hatte Angst vor Ihrem Punkt a), aber ich wusste nicht genau, ob das der Fall war. Außerdem läuft die Abfrage unter 0,2 Sekunden sogar noch schneller, aber wie Sie erraten haben, muss ich diese Abfrage millionenfach ausführen. Ich habe etwas umgeschrieben und ich habe jetzt eine Lösung, die die Abfragen mit einigen 'ORDER BY' und' (size = @size ODER Größe IS NULL) 'magic. –

Antwort

1

Zunächst einige Probleme ...

  • UNION baut immer eine tmp Tabelle. (Diese Ineffizienz wird, soweit praktikabel, in MySQL 5.7.3 und MariaDB 10.1 eliminiert).
  • Die Abfrage fehlt eine ORDER BY am Ende - diese könnte dazu führen, dass die falsche Antwort bekommen.
  • Eine zweite TMP-Tabelle wäre für die äußere ORDER BY erforderlich.

Jetzt einige Verbesserungsvorschläge. Ohne mehr über die Daten zu wissen, muss ich sagen, dass diese vielleicht schneller laufen oder nicht.

Vermeidung *:

Statt SELECT * zu tun, nur SELECT id und dann JOIN an den Tisch zurück, den Rest der Spalten zu erhalten:

SELECT b.* 
    FROM (SELECT id ... UNION ALL ... LIMIT 1) AS a 
    JOIN product_quality AS b USING(id); 

Weitere Indizes:

INDEX(quality, size, price) 
INDEX(quality, price, category) 
... 

Führen Sie einen einzelnen Tabellenscan durch. kein Index benötigt. (Dies erfordert, dass quality Werte geordnet.):

SELECT * FROM ... 
    WHERE (quality = 'A1' AND size = @size AND price = @price ...) 
     OR (quality = 'A3' AND price = @price AND category = @category ...) 
    ORDER BY quality 
    LIMIT 1 

(Normalerweise empfehle ich OR durch UNION für die Leistung zu ersetzen, aber ich denke, Ihr Anwendungsfall funktioniert anders.)

CASE:

Ihre ersten beiden wählt kombiniert werden könnten:

SELECT MIN(IF(weight = @weight, 'A1', 'A2')) AS quality 
    WHERE size = @size 
     AND price = @price 
     AND category = @category 
     AND origin = @origin) 
+0

UNION ALL ist auch optimiert, um eine temporäre Tabelle möglichst in MySQL 5.7.3 zu vermeiden. https://bugs.mysql.com/bug.php?id=50674 –

+0

Vielen Dank für Ihre ausführliche Antwort. Ich verwende tatsächlich spezifische Spalten anstelle von *, aber der Einfachheit halber habe ich die Spalten weggelassen, vielleicht hätte ich das erwähnen sollen. Ihre OR-Anfrage ist genau das, wonach ich suche, aber ich denke, die DB wird immer noch alle Fälle prüfen, bevor sie die mit der höchsten Qualität zurückgibt, richtig? Gibt es nicht einen Weg, wo, wenn die A1-Qualität übereinstimmt, es sofort zurückkehrt, anstatt die anderen Fälle zu betrachten? –

Verwandte Themen