Het wordt meestal aangeraden om DISTINCT
. te gebruiken in plaats van GROUP BY
, aangezien dat is wat u eigenlijk wilt, en laat de optimizer het "beste" uitvoeringsplan kiezen. Geen enkele optimizer is echter perfect. DISTINCT
gebruiken de optimizer kan meer opties hebben voor een uitvoeringsplan. Maar dat betekent ook dat het meer opties heeft om een slecht plan te kiezen .
U schrijft dat de DISTINCT
query is "langzaam", maar je vertelt geen cijfers. In mijn test (met 10 keer zoveel rijen op MariaDB 10.0.19 en 10.3.13 ) de DISTINCT
query is (slechts) 25% langzamer (562 ms/453 ms). De EXPLAIN
resultaat is helemaal geen hulp. Het is zelfs "liegen". Met LIMIT 100, 30
het zou minstens 130 rijen moeten lezen (dat is wat mijn EXPLAIN
. is) eigenlijk schijnt voor GROUP BY
), maar het toont je 65.
Ik kan het verschil van 25% in uitvoeringstijd niet verklaren, maar het lijkt erop dat de engine in ieder geval een volledige tabel/indexscan uitvoert en het resultaat sorteert voordat het 100 kan overslaan en 30 rijen kan selecteren.
Het beste plan zou waarschijnlijk zijn:
- Lees rijen van
idx_reg_date
index (tabelA
) één voor één in aflopende volgorde - Kijk of er een overeenkomst is in de
idx_order_id
index (tabelB
) - Sla 100 overeenkomende rijen over
- Stuur 30 overeenkomende rijen
- Afsluiten
Als er ongeveer 10% rijen in A
. zijn die geen overeenkomst hebben in B
, zou dit plan zoiets als 143 rijen uit A
. lezen .
Het beste wat ik kan doen om dit plan op de een of andere manier te forceren is:
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
Deze query retourneert hetzelfde resultaat in 156 ms (3 keer sneller dan GROUP BY
). Maar dat gaat nog te traag. En hij leest waarschijnlijk nog steeds alle rijen in tabel A
.
We kunnen bewijzen dat er een beter plan kan bestaan met een "kleine" subquery-truc:
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
Deze query wordt in "no time" (~ 0 ms) uitgevoerd en geeft hetzelfde resultaat op mijn testgegevens. En hoewel het niet 100% betrouwbaar is, laat het zien dat de optimizer het niet goed doet.
Dus wat zijn mijn conclusies:
- De optimizer doet niet altijd het beste werk en heeft soms hulp nodig
- Zelfs als we "het beste plan" kennen, kunnen we het niet altijd afdwingen
DISTINCT
is niet altijd sneller danGROUP BY
- Als er geen index voor alle clausules kan worden gebruikt, wordt het behoorlijk lastig
Testschema en dummygegevens:
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;
Vragen:
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