Det anbefales normalt at bruge DISTINCT
i stedet for GROUP BY
, da det er det du faktisk ønsker, og lad optimizeren vælge den "bedste" eksekveringsplan. Dog - ingen optimizer er perfekt. Brug af DISTINCT
optimizeren kan have flere muligheder for en eksekveringsplan. Men det betyder også, at den har flere muligheder for at vælge en dårlig plan .
Du skriver, at DISTINCT
forespørgslen er "langsom", men du fortæller ingen tal. I min test (med 10 gange så mange rækker på MariaDB 10.0.19 og 10.3.13 ) DISTINCT
forespørgslen er ligesom (kun) 25 % langsommere (562ms/453ms). EXPLAIN
resultatet er ingen hjælp overhovedet. Det er endda "løgn". Med LIMIT 100, 30
den skal læse mindst 130 rækker (det er hvad min EXPLAIN
bruger faktisk GROUP BY
), men det viser dig 65.
Jeg kan ikke forklare forskellen på 25 % i udførelsestid, men det ser ud til, at motoren under alle omstændigheder laver en fuld tabel/indeksscanning og sorterer resultatet, før den kan springe 100 over og vælge 30 rækker.
Den bedste plan ville sandsynligvis være:
- Læs rækker fra
idx_reg_date
indeks (tabelA
) én efter én i faldende rækkefølge - Se, om der er et match i
idx_order_id
indeks (tabelB
) - Spring 100 matchende rækker over
- Send 30 matchende rækker
- Afslut
Hvis der er 10 % af rækkerne i A
som ikke har nogen match i B
, ville denne plan læse noget i retning af 143 rækker fra A
.
Det bedste, jeg kunne gøre for at fremtvinge denne plan, er:
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
Denne forespørgsel returnerer det samme resultat på 156 ms (3 gange hurtigere end GROUP BY
). Men det går stadig for langsomt. Og den læser sandsynligvis stadig alle rækker i tabel A
.
Vi kan bevise, at en bedre plan kan eksistere med et "lille" underforespørgselstrick:
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
Denne forespørgsel udføres på "no time" (~ 0 ms) og returnerer det samme resultat på mine testdata. Og selvom den ikke er 100 % pålidelig, viser den, at optimeringsværktøjet ikke gør et godt stykke arbejde.
Så hvad er mine konklusioner:
- Optimeringsværktøjet gør ikke altid det bedste stykke arbejde og har nogle gange brug for hjælp
- Selv når vi kender "den bedste plan", kan vi ikke altid håndhæve den
DISTINCT
er ikke altid hurtigere endGROUP BY
- Når intet indeks kan bruges til alle klausuler - bliver tingene ret vanskelige
Testskema og dummydata:
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;
Forespørgsler:
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