Obvykle se doporučuje použít DISTINCT
místo GROUP BY
, protože to je to, co skutečně chcete, a nechte optimalizátora vybrat "nejlepší" plán provádění. Nicméně – žádný optimalizátor není dokonalý. Pomocí DISTINCT
optimalizátor může mít více možností pro plán provádění. To ale také znamená, že má vícemožností vybrat špatný plán .
Píšete, že DISTINCT
dotaz je "pomalý", ale neříkáte žádná čísla. V mém testu (s 10krát větším počtem řádků na MariaDB 10.0.19 a 10.3.13 ) DISTINCT
dotaz je jako (jen) o 25 % pomalejší (562 ms/453 ms). EXPLAIN
výsledek vůbec nepomáhá. Je to dokonce "lhaní". S LIMIT 100, 30
potřebovalo by to přečíst alespoň 130 řádků (to je můj EXPLAIN
ve skutečnosti zobrazuje GROUP BY
), ale ukazuje vám 65.
Nedokážu vysvětlit 25% rozdíl v době provádění, ale zdá se, že engine v každém případě provádí úplné skenování tabulky/indexu a seřadí výsledek, než může přeskočit 100 a vybrat 30 řádků.
Nejlepší plán by pravděpodobně byl:
- Čtení řádků z
idx_reg_date
index (tabulkaA
) jeden po druhém v sestupném pořadí - Podívejte se, zda existuje shoda v
idx_order_id
index (tabulkaB
) - Přeskočit 100 odpovídajících řádků
- Odeslat 30 odpovídajících řádků
- Ukončit
Pokud je v A
přibližně 10 % řádků které nemají shodu v B
, tento plán by četl něco jako 143 řádků z A
.
Nejlepší, co mohu udělat, abych tento plán nějak prosadil, je:
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
Tento dotaz vrátí stejný výsledek za 156 ms (3krát rychleji než GROUP BY
). Ale to je stále příliš pomalé. A pravděpodobně stále čte všechny řádky v tabulce A
.
Že může existovat lepší plán, můžeme dokázat „malým“ trikem s poddotazy:
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
Tento dotaz se provede „bez času“ (~ 0 ms) a vrátí stejný výsledek na mých testovacích datech. A ačkoli to není 100% spolehlivé, ukazuje to, že optimalizátor nedělá dobrou práci.
Jaké jsou tedy mé závěry:
- Optimalizátor neodvádí vždy nejlepší práci a někdy potřebuje pomoc
- I když známe „nejlepší plán“, nemůžeme ho vždy prosadit
DISTINCT
není vždy rychlejší nežGROUP BY
- Když nelze použít žádný index pro všechny klauzule, věci začínají být docela složité
Testovací schéma a fiktivní data:
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;
Dotazy:
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