Det rekommenderas vanligtvis att använda DISTINCT
istället för GROUP BY
, eftersom det är vad du faktiskt vill, och låt optimeraren välja den "bästa" exekveringsplanen. Men ingen optimerare är perfekt. Använder DISTINCT
optimeraren kan ha fler alternativ för en genomförandeplan. Men det betyder också att den har fler alternativ för att välja en dålig plan .
Du skriver att DISTINCT
frågan är "långsam", men du berättar inga siffror. I mitt test (med 10 gånger så många rader på MariaDB 10.0.19 och 10.3.13 ) DISTINCT
frågan är ungefär (bara) 25 % långsammare (562ms/453ms). EXPLAIN
resultatet är ingen hjälp alls. Det är till och med att "ljuga". Med LIMIT 100, 30
den skulle behöva läsa minst 130 rader (det är vad min EXPLAIN
läser faktiskt GROUP BY
), men det visar dig 65.
Jag kan inte förklara skillnaden på 25 % i exekveringstid, men det verkar som att motorn gör en fullständig tabell-/indexskanning i alla fall och sorterar resultatet innan den kan hoppa över 100 och välja 30 rader.
Den bästa planen skulle förmodligen vara:
- Läs rader från
idx_reg_date
index (tabellA
) en efter en i fallande ordning - Titta om det finns en matchning i
idx_order_id
index (tabellB
) - Hoppa över 100 matchande rader
- Skicka 30 matchande rader
- Avsluta
Om det finns ungefär 10 % av raderna i A
som inte har någon matchning i B
, skulle den här planen läsa ungefär 143 rader från A
.
Det bästa jag kan göra för att på något sätt tvinga fram den här planen är:
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
Den här frågan returnerar samma resultat på 156 ms (3 gånger snabbare än GROUP BY
). Men det går fortfarande för långsamt. Och den läser förmodligen fortfarande alla rader i tabell A
.
Vi kan bevisa att en bättre plan kan existera med ett "litet" subquery-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
Den här frågan körs på "notid" (~ 0 ms) och returnerar samma resultat på mina testdata. Och även om den inte är 100 % tillförlitlig, visar den att optimeraren inte gör ett bra jobb.
Så vad är mina slutsatser:
- Optimeraren gör inte alltid det bästa jobbet och behöver ibland hjälp
- Även när vi känner till "den bästa planen" kan vi inte alltid tillämpa den
DISTINCT
är inte alltid snabbare änGROUP BY
- När inget index kan användas för alla satser – det börjar bli ganska knepigt
Testa schema och 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;
Frågor:
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