Zwykle zaleca się użycie DISTINCT
zamiast GROUP BY
, ponieważ tego właśnie chcesz, i pozwól optymalizatorowi wybrać „najlepszy” plan wykonania. Jednak żaden optymalizator nie jest doskonały. Używanie DISTINCT
optymalizator może mieć więcej opcji planu wykonania. Ale oznacza to również, że ma więcej opcji wyboru złego planu .
Piszesz, że DISTINCT
zapytanie jest „wolne”, ale nie podajesz żadnych liczb. W moim teście (z 10 razy większą liczbą wierszy w MariaDB 10.0.19 i 10.3.13 ) DISTINCT
zapytanie jest (tylko) 25% wolniejsze (562ms/453ms). EXPLAIN
wynik w ogóle nie pomaga. To nawet „kłamie”. Z LIMIT 100, 30
musiałby przeczytać co najmniej 130 wierszy (to właśnie moje EXPLAIN
faktycznie pokazuje dla GROUP BY
), ale pokazuje 65.
Nie potrafię wyjaśnić 25% różnicy w czasie wykonania, ale wygląda na to, że silnik i tak wykonuje pełne skanowanie tabeli/indeksu i sortuje wynik, zanim będzie mógł pominąć 100 i wybrać 30 wierszy.
Najlepszym planem byłoby prawdopodobnie:
- Odczytaj wiersze z
idx_reg_date
indeks (tabelaA
) jeden po drugim w kolejności malejącej - Sprawdź, czy istnieje dopasowanie w
idx_order_id
indeks (tabelaB
) - Pomiń 100 pasujących wierszy
- Wyślij 30 pasujących wierszy
- Wyjdź
Jeśli jest około 10% wierszy w A
które nie pasują do B
, ten plan odczytałby mniej więcej 143 wiersze z A
.
Najlepsze, co mógłbym zrobić, aby jakoś wymusić ten plan, to:
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
To zapytanie zwraca ten sam wynik w 156 ms (3 razy szybciej niż GROUP BY
). Ale to wciąż jest zbyt wolne. I prawdopodobnie nadal odczytuje wszystkie wiersze w tabeli A
.
Możemy udowodnić, że lepszy plan może istnieć za pomocą „małej” sztuczki z podzapytaniem:
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
To zapytanie jest wykonywane „bez czasu” (~ 0 ms) i zwraca ten sam wynik w moich danych testowych. I chociaż nie jest w 100% niezawodny, pokazuje, że optymalizator nie wykonuje dobrej pracy.
Więc jakie są moje wnioski:
- Optymalizator nie zawsze wykonuje najlepszą pracę i czasami potrzebuje pomocy
- Nawet jeśli znamy „najlepszy plan”, nie zawsze możemy go egzekwować
DISTINCT
nie zawsze jest szybszy niżGROUP BY
- Kiedy żaden indeks nie może być użyty dla wszystkich klauzul — sprawy stają się dość skomplikowane
Schemat testowy i dane fikcyjne:
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;
Zapytania:
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