Unterabfragen werden einzeln sehr schnell ausgeführt, sind jedoch beim Verbinden sehr langsam

7

ypercube hat das Problem gelöst. Unterabfragen waren völlig unnötig, und das Ganze funktioniert mit einfachen Verknüpfungen. Es ist immer noch seltsam, dass der Optimierer von MySQL meine ursprüngliche Abfrage nicht verwenden konnte. Siehe unten für die Frage und viele Details. Plus eine vollständige Lösung am Ende meiner Frage. Es basiert auf der Antwort von ypercube.

Jede Unterabfrage ist sehr schnell, deutlich unter 1 Sekunde. Die 5-6 Unterabfragen werden zusammengefügt (einige LEFT, einige INNER) und die Zeit steigt auf 400 Sekunden.

Die Gesamtabfrage, die ich zum Testen verwende, gibt nur 441 Zeilen zurück.

Ich habe versucht, jede der Unterabfragen in eine "CREATE TABLE" -Abfrage einzufügen. Jeder war in weniger als 1 Sekunde erledigt. Dann habe ich die äußere Abfrage mit diesen neu erstellten Tabellen überarbeitet und sie lief auch in weniger als 1 Sekunde. Es gibt also kein wirkliches Problem mit den Joins. Ich lege Indizes idfür meine erstellten Tabellen an. Alle Tabellen werden beim Matching id= verknüpft id.

Wie kann ich MySQL veranlassen, die Abfrage effizient auszuführen? Muss ich temporäre Tabellen verwenden? Ich habe bereits eine Menge PHP-Code geschrieben, um die mehreren Unterabfrage-Joins zusammenzustellen, sodass ich lieber nur herausfinden möchte, wie dies funktioniert, wenn möglich.

Ich habe versucht, das Schlüsselwort "STRAIGHT_JOIN" zu verwenden und das äußere zu entfernen ORDER BY. Dadurch wurde die Abfragezeit auf 90 Sekunden reduziert. Aber ich sollte maximal 1s bekommen.

Ich habe es STRAIGHT_JOINmit versucht ORDER BYund es dauerte 235 Sekunden. Es scheint also, dass das Äußere ORDER BYein großes Leistungsproblem darstellt.

BEARBEITEN:

Getestet mit temporären Tabellen. Die Abfrage läuft sehr schnell. Aber es muss einen Weg geben, MySQL so schnell mit JOINS zu machen.

Das langsame Abfrageprotokoll zeigt außerdem:

Rows_examined: 484006914

484 Millionen Zeilen sehen aus wie ein kartesisches Produkt. Warum werden so viele Zeilen untersucht?

Die Abfrage hat folgende Struktur:

SELECT t0.`id`, t1.`length`, t2.`height`, t3.`family`
FROM
`products` t0
INNER JOIN
(
SELECT t1.`id`, t2.`value` AS `length`
FROM `products` t1
INNER JOIN `product_eav_decimal` t2
ON t1.`id` = t2.`product_id`
WHERE t2.`attribute_id` = 91
AND t2.`value` BETWEEN 15 AND 35
) t1

ON t0.`id` = t1.`id`

LEFT JOIN
(
SELECT t1.`id`, t2.`value` AS `height`
FROM `products` t1
INNER JOIN `product_eav_decimal` t2
ON t1.`id` = t2.`product_id`
WHERE t2.`attribute_id` = 80
# no other conditions
) t2
ON t0.`id` = t2.`id`

INNER JOIN
(
.
.
.
) t6
ON t0.`id` = t6.`id`
ORDER BY t0.`id` ASC

... etc LEFT JOINS werden verwendet, wenn keine anderen Bedingungen in der Unterabfrage als die attribute_id vorhanden sind. INNER JOIN wird verwendet, wenn eine andere Bedingung vorliegt. Dadurch wird ein gültiges Suchergebnis erstellt. Die Abfrage funktioniert, es dauert nur 400 Sekunden anstatt 0,04.

Wenn niemand weiß, wie die JOIN-Syntax funktioniert, verwende ich temporäre Tabellen, da dies zu funktionieren scheint.

TABELLEN:

1.) Produkte

CREATE TABLE `products` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `sku` varchar(127) NOT NULL COMMENT '3char vencode + model',
 `model` varchar(127) NOT NULL,
 `vendor_id` int(11) DEFAULT NULL,
 `updated` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 UNIQUE KEY `sku` (`sku`),
 KEY `model` (`model`),
 KEY `vendor_id` (`vendor_id`),
 CONSTRAINT `FK1` FOREIGN KEY (`vendor_id`) REFERENCES `vendors` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=153282 DEFAULT CHARSET=utf8

2.) Dezimalstellen

CREATE TABLE `product_eav_decimal` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `product_id` int(11) NOT NULL,
 `attribute_id` int(11) DEFAULT NULL,
 `value` decimal(11,3) DEFAULT NULL,
 `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 UNIQUE KEY `natural_key` (`product_id`,`attribute_id`,`value`),
 UNIQUE KEY `product_id_2` (`product_id`,`attribute_id`),
 KEY `last_update` (`last_update`),
 KEY `product_id` (`product_id`),
 KEY `attribute_id` (`attribute_id`),
 KEY `value` (`value`),
 CONSTRAINT `FK1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
 CONSTRAINT `FK2` FOREIGN KEY (`attribute_id`) REFERENCES `attributes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=370772 DEFAULT CHARSET=utf8 COLLATE=utf8_bin

3.) varchar (verweist auf eine andere Tabelle, values_varcharTabelle für tatsächliche varchar-Werte)

CREATE TABLE `product_eav_varchar` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `product_id` int(11) DEFAULT NULL,
 `attribute_id` int(11) DEFAULT NULL,
 `value_id` int(11) DEFAULT NULL,
 `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 UNIQUE KEY `natural_key` (`product_id`,`attribute_id`,`value_id`),
 KEY `last_update` (`last_update`),
 KEY `product_id` (`product_id`),
 KEY `value_id` (`value_id`),
 KEY `attribute_id` (`attribute_id`),
 CONSTRAINT `FK1` FOREIGN KEY (`value_id`) REFERENCES `values_varchar` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
 CONSTRAINT `FK2` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
 CONSTRAINT `FK3` FOREIGN KEY (`attribute_id`) REFERENCES `attributes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=86049 DEFAULT CHARSET=utf8 COLLATE=utf8_bin

Angepasst an die Antwort von ypercube:

SELECT t0.id, 
       t1.`value` AS length, 
       t2.`value` AS height, 
       t3.`value` AS family,
       t5.`value` AS type
FROM
  products t0

INNER JOIN # INNER used when search criteria
# length (only searched values)
  product_eav_decimal t1
    ON  t1.product_id = t0.id  
    AND t1.attribute_id = 91
    AND t1.`value` BETWEEN 15 AND 35 # search criteria

LEFT JOIN # LEFT used when no search criteria
# height (all, including blank/null)
  product_eav_decimal t2
    ON  t2.product_id = t0.id  
    AND t2.attribute_id = 80  

LEFT JOIN  # LEFT - no search critera
# family - varchar type requires extra join to values table
  product_eav_varchar t3
    ON  t3.product_id = t0.id  
    AND t3.attribute_id = 77
LEFT JOIN # LEFT join to values table matches eav table join
values_varchar t4
    ON t3.value_id = t4.id
# search criteria would be here. see next

INNER JOIN # INNER - search criteria below
# type - varchar requires extra join, see below
  product_eav_varchar t5
    ON t5.product_id = t0.id
    AND t5.attribute_id = 76
INNER JOIN # INNER join to values table matches eav table join
values_varchar t6
    ON t5.value_id = t6.id
    # search criteria
    AND (t6.value LIKE "%sofa%" COLLATE utf8_general_ci OR t6.value LIKE "%chair%" COLLATE utf8_general_ci)

ORDER BY t0.id ASC;

Die Abfrage funktioniert. Es läuft in wenigen Millisekunden. Wenn Suchbegriffe oder Bereichsbeschränkungen angegeben werden, werden NUR übereinstimmende Ergebnisse mit INNER JOINs zurückgegeben. Wenn es keine Kriterien gibt, werden LEFT JOINs verwendet, um Werte zurückzugeben (einschließlich NULL / leer).

Update August 2014 - Die productsTabelle enthält jetzt 400 bis 500.000 Zeilen, und der oben verwendete Abfragestil wird immer noch blitzschnell ausgeführt. Es scheint, dass Joins in MySQL viel schneller sind als Unterabfragen.

Buttle Butkus
quelle
Unterstützt MySQL CTEs?
Dreamlax
Eigentlich bin ich mir nicht mal sicher, ob das helfen würde ...
dreamlax
@dreamlax: Ich sehe nicht, wie sie helfen würden, selbst wenn MySQL sie unterstützen würde (was es nicht tut)
a_horse_with_no_name
MySQL erstellt angeblich temporäre Tabellen während der Ausführung der Abfrage. Aber was auch immer es tut, es macht es falsch (oder ich bin es).
Buttle Butkus
Fügen Sie die Definitionen ( SHOW CREATE TABLEAusgabe) für die 2 Tabellen hinzu.
Ypercubeᵀᴹ

Antworten:

6

Sie benötigen nicht alle abgeleiteten Tabellen. Sie treten productzu oft dem basic ( ) bei. Sie können die Abfrage nur einmal schreiben.

Zusammengesetzte Indizes sind ein Muss für EAV-Designs. Versuchen Sie, einen Index (attribute_id, product_id, value)und dann die Abfrage hinzuzufügen :

SELECT t0.id, 
       t1.`value` AS length, 
       t2.`value` AS height, 
       t3.`value` AS family
FROM
  products t0

INNER JOIN 
  product_eav_decimal t1
    ON  t1.product_id = t0.id  
    AND t1.attribute_id = 91
    AND t1.`value` BETWEEN 15 AND 35

LEFT JOIN
  product_eav_decimal t2
    ON  t2.product_id = t0.id  
    AND t2.attribute_id = 80  
-- 
-- 
--

LEFT JOIN                              -- LEFT or INNER join
  product_eav_decimal t6
    ON  t6.product_id = t0.id  
 -- AND t6.attribute_id = 

ORDER BY t0.id ASC ;
ypercubeᵀᴹ
quelle
Danke, das funktioniert ganz gut mit den numerischen Tabellen. Ich werde es morgen mit varchar testen und hoffentlich wird es funktionieren. Aber 0,0002s für 3 Dezimal-Joins sind ein vielversprechender Start!
Buttle Butkus
Vielen Dank! Ich habe gerade einen Test mit 7 JOINs durchgeführt und er lief in 0.0318s. Ich kann mir vorstellen, dass 7 ein paar Jahre gedauert hätte. Das ist also eine ziemliche Verbesserung. Ich bin immer noch überrascht, dass MySQL meine ursprüngliche Abfrage nicht beschleunigen konnte, aber diese Lösung ist trotzdem viel eleganter.
Buttle Butkus
Haben Sie Ihre ursprüngliche Abfrage versucht, nachdem der Index hinzugefügt wurde? Selbst wenn es auch beschleunigt wird (was sein sollte), gibt es keinen Grund, 14 Joins zu machen, wenn es mit 7 geschrieben werden kann.
ypercubeᵀᴹ
Ich habe die ursprüngliche Abfrage nach dem Hinzufügen von zusammengesetzten Indizes versucht, aber keinen Unterschied festgestellt. Alle einzelnen Spalten wurden indiziert. Zusammengesetzte Indizes machten keinen merklichen Unterschied. Ich glaube nicht, dass das Problem in der Anzahl der Joins lag, sondern in den Unterabfragen. Es scheint, dass MySQL keine Indizes in Unterabfragen verwenden kann. 300 Sekunden gegenüber 3 Millisekunden sind ein unglaublicher Unterschied in der Effizienz. Ich glaube nicht, dass ich Probleme haben werde, 20 bis 30 Joins auf diese Weise mit Hunderttausenden von Zeilen durchzuführen (und bald werde ich es sein).
Buttle Butkus