Обикновено се препоръчва да използвате DISTINCT
вместо GROUP BY
, тъй като това всъщност искате и оставете оптимизатора да избере "най-добрия" план за изпълнение. Въпреки това - нито един оптимизатор не е перфектен. Използване на DISTINCT
оптимизаторът може да има повече опции за план за изпълнение. Но това също означава, че има повечеопции за избор на лош план .
Пишете, че DISTINCT
заявката е "бавна", но не казвате никакви числа. В моя тест (с 10 пъти повече реда на MariaDB 10.0.19 и 10.3.13 ) DISTINCT
заявката е като (само) 25% по-бавна (562ms/453ms). EXPLAIN
резултатът изобщо не помага. Дори е "лъже". С LIMIT 100, 30
ще трябва да прочете поне 130 реда (това е моето EXPLAIN
всъщност показва за GROUP BY
), но ви показва 65.
Не мога да обясня разликата от 25% във времето за изпълнение, но изглежда, че машината прави пълно сканиране на таблица/индекс във всеки случай и сортира резултата, преди да може да пропусне 100 и да избере 30 реда.
Най-добрият план вероятно би бил:
- Четене на редове от
idx_reg_date
индекс (таблицаA
) един по един в низходящ ред - Вижте дали има съвпадение в
idx_order_id
индекс (таблицаB
) - Пропуснете 100 съвпадащи реда
- Изпратете 30 съвпадащи реда
- Изход
Ако има около 10% редове в A
които нямат съвпадение в B
, този план ще прочете нещо като 143 реда от A
.
Най-доброто, което мога да направя, за да наложа по някакъв начин този план, е:
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 (3 пъти по-бързо от GROUP BY
). Но това все още е твърде бавно. И вероятно все още чете всички редове в таблица A
.
Можем да докажем, че може да съществува по-добър план с „малък“ трик с подзаявка:
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) и връща същия резултат за моите тестови данни. И въпреки че не е 100% надежден, това показва, че оптимизаторът не върши добре работата.
И така, какви са моите заключения:
- Оптимизаторът не винаги върши най-добрата работа и понякога има нужда от помощ
- Дори когато знаем „най-добрия план“, не винаги можем да го приложим
DISTINCT
не винаги е по-бърз отGROUP BY
- Когато не може да се използва индекс за всички клаузи - нещата стават доста трудни
Тестова схема и фиктивни данни:
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;
Запитвания:
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