Normalerweise wird empfohlen, DISTINCT
zu verwenden statt GROUP BY
, da Sie das eigentlich wollen, und lassen Sie den Optimierer den "besten" Ausführungsplan auswählen. Allerdings - kein Optimierer ist perfekt. Verwendung von DISTINCT
der Optimierer kann mehr Optionen für einen Ausführungsplan haben. Aber das bedeutet auch, dass es mehr Optionen hat, einen schlechten Plan zu wählen .
Sie schreiben das DISTINCT
Die Abfrage ist "langsam", aber Sie nennen keine Zahlen. In meinem Test (mit 10-mal so vielen Zeilen auf MariaDB 10.0.19 und 10.3.13 ) der DISTINCT
Abfrage ist wie (nur) 25% langsamer (562ms/453ms). Das EXPLAIN
Ergebnis ist überhaupt keine Hilfe. Es ist sogar "lügen". Mit LIMIT 100, 30
es müsste mindestens 130 Zeilen lesen (das ist mein EXPLAIN
zeigt eigentlich GROUP BY
an ), aber es zeigt Ihnen 65.
Ich kann den Unterschied von 25 % in der Ausführungszeit nicht erklären, aber es scheint, dass die Engine in jedem Fall einen vollständigen Tabellen-/Index-Scan durchführt und das Ergebnis sortiert, bevor sie 100 überspringen und 30 Zeilen auswählen kann.
Der beste Plan wäre wahrscheinlich:
- Zeilen von
idx_reg_date
lesen Index (TabelleA
) nacheinander in absteigender Reihenfolge - Schauen Sie, ob es eine Übereinstimmung in der
idx_order_id
gibt Index (TabelleB
) - 100 übereinstimmende Zeilen überspringen
- Sende 30 übereinstimmende Zeilen
- Beenden
Wenn etwa 10 % der Zeilen in A
vorhanden sind die keine Übereinstimmung in B
haben , würde dieser Plan ungefähr 143 Zeilen von A
lesen .
Das Beste, was ich tun könnte, um diesen Plan irgendwie zu erzwingen, ist:
SELECT A.id
FROM `order` A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30
OFFSET 100
Diese Abfrage gibt das gleiche Ergebnis in 156 ms zurück (dreimal schneller als GROUP BY
). Aber das ist noch zu langsam. Und es liest wahrscheinlich immer noch alle Zeilen in Tabelle A
.
Dass es einen besseren Plan geben kann, beweisen wir mit einem "kleinen" Unterabfrage-Trick:
SELECT A.id
FROM (
SELECT id, reg_date
FROM `order`
ORDER BY reg_date DESC
LIMIT 1000
) A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30
OFFSET 100
Diese Abfrage wird "in kürzester Zeit" (~ 0 ms) ausgeführt und gibt das gleiche Ergebnis für meine Testdaten zurück. Und obwohl es nicht 100 % zuverlässig ist, zeigt es doch, dass der Optimierer keine gute Arbeit leistet.
Also, was sind meine Schlussfolgerungen:
- Der Optimierer leistet nicht immer die beste Arbeit und benötigt manchmal Hilfe
- Selbst wenn wir "den besten Plan" kennen, können wir ihn nicht immer durchsetzen
DISTINCT
ist nicht immer schneller alsGROUP BY
- Wenn kein Index für alle Klauseln verwendet werden kann, wird es ziemlich knifflig
Testschema und Dummy-Daten:
drop table if exists `order`;
CREATE TABLE `order` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`reg_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_reg_date` (`reg_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
insert into `order`(reg_date)
select from_unixtime(floor(rand(1) * 1000000000)) as reg_date
from information_schema.COLUMNS a
, information_schema.COLUMNS b
limit 218860;
drop table if exists `order_detail_products`;
CREATE TABLE `order_detail_products` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`order_id` bigint(20) unsigned NOT NULL,
`order_detail_id` int(11) NOT NULL,
`prod_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_order_detail_id` (`order_detail_id`,`prod_id`),
KEY `idx_order_id` (`order_id`,`order_detail_id`,`prod_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
insert into order_detail_products(id, order_id, order_detail_id, prod_id)
select null as id
, floor(rand(2)*218860)+1 as order_id
, 0 as order_detail_id
, 0 as prod_id
from information_schema.COLUMNS a
, information_schema.COLUMNS b
limit 437320;
Abfragen:
SELECT DISTINCT A.id
FROM `order` A
JOIN order_detail_products B ON A.id = B.order_id
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- 562 ms
SELECT A.id
FROM `order` A
JOIN order_detail_products B ON A.id = B.order_id
GROUP BY A.id
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- 453 ms
SELECT A.id
FROM `order` A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- 156 ms
SELECT A.id
FROM (
SELECT id, reg_date
FROM `order`
ORDER BY reg_date DESC
LIMIT 1000
) A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- ~ 0 ms