Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Eindeutig vs. Gruppieren nach

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 (Tabelle A ) nacheinander in absteigender Reihenfolge
  • Schauen Sie, ob es eine Übereinstimmung in der idx_order_id gibt Index (Tabelle B )
  • 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 als GROUP 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