Mysql
 sql >> Database >  >> RDS >> Mysql

Distinta vs Raggruppa per

Di solito si consiglia di utilizzare DISTINCT invece di GROUP BY , poiché è ciò che desideri effettivamente e lascia che l'ottimizzatore scelga il piano di esecuzione "migliore". Tuttavia, nessun ottimizzatore è perfetto. Usando DISTINCT l'ottimizzatore può avere più opzioni per un piano di esecuzione. Ma ciò significa anche che ha più opzioni per scegliere un piano sbagliato .

Scrivi che il DISTINCT la query è "lenta", ma non dici alcun numero. Nel mio test (con 10 volte più righe su MariaDB 10.0.19 e 10.3.13 ) il DISTINCT la query è come (solo) il 25% più lenta (562 ms/453 ms). Il EXPLAIN il risultato non è di alcun aiuto. È persino "mentire". Con LIMIT 100, 30 dovrebbe leggere almeno 130 righe (questo è ciò che il mio EXPLAIN in realtà mostra GROUP BY ), ma ti mostra 65.

Non riesco a spiegare la differenza del 25% nel tempo di esecuzione, ma sembra che il motore stia comunque eseguendo una scansione completa di tabella/indice e ordina il risultato prima di poter saltare 100 e selezionare 30 righe.

Il miglior piano sarebbe probabilmente:

  • Leggi le righe da idx_reg_date indice (tabella A ) uno per uno in ordine decrescente
  • Guarda se c'è una corrispondenza nel idx_order_id indice (tabella B )
  • Salta 100 righe corrispondenti
  • Invia 30 righe corrispondenti
  • Esci

Se ci sono circa il 10% delle righe in A che non hanno corrispondenza in B , questo piano leggerebbe qualcosa come 143 righe da A .

Il meglio che potrei fare per forzare in qualche modo questo piano è:

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

Questa query restituisce lo stesso risultato in 156 ms (3 volte più veloce di GROUP BY ). Ma è ancora troppo lento. E probabilmente sta ancora leggendo tutte le righe nella tabella A .

Possiamo provare che può esistere un piano migliore con un "piccolo" trucco di subquery:

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

Questa query viene eseguita in "nessun tempo" (~ 0 ms) e restituisce lo stesso risultato sui miei dati di test. E sebbene non sia affidabile al 100%, mostra che l'ottimizzatore non sta facendo un buon lavoro.

Allora quali sono le mie conclusioni:

  • L'ottimizzatore non sempre fa il lavoro migliore e talvolta ha bisogno di aiuto
  • Anche quando conosciamo "il miglior piano", non sempre possiamo farlo rispettare
  • DISTINCT non è sempre più veloce di GROUP BY
  • Quando nessun indice può essere utilizzato per tutte le clausole, le cose stanno diventando piuttosto complicate

Schema di test e dati fittizi:

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;

Query:

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