sql >> Databáze >  >> RDS >> Mysql

Odlišné vs

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 (tabulka A ) jeden po druhém v sestupném pořadí
  • Podívejte se, zda existuje shoda v idx_order_id index (tabulka B )
  • 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



  1. Transponování dynamických sloupců do řádků

  2. Syntaxe MySQL Chyba správné syntaxe pro použití blízko 'desc

  3. Spusťte dotaz SQL při spuštění služby MySQL

  4. Jak vygenerovat příkaz create table sql pro existující tabulku v postgreSQL