sql >> Database >  >> RDS >> Mysql

Onderscheiden versus groeperen op basis van

Het wordt meestal aangeraden om DISTINCT . te gebruiken in plaats van GROUP BY , aangezien dat is wat u eigenlijk wilt, en laat de optimizer het "beste" uitvoeringsplan kiezen. Geen enkele optimizer is echter perfect. DISTINCT gebruiken de optimizer kan meer opties hebben voor een uitvoeringsplan. Maar dat betekent ook dat het meer opties heeft om een ​​slecht plan te kiezen .

U schrijft dat de DISTINCT query is "langzaam", maar je vertelt geen cijfers. In mijn test (met 10 keer zoveel rijen op MariaDB 10.0.19 en 10.3.13 ) de DISTINCT query is (slechts) 25% langzamer (562 ms/453 ms). De EXPLAIN resultaat is helemaal geen hulp. Het is zelfs "liegen". Met LIMIT 100, 30 het zou minstens 130 rijen moeten lezen (dat is wat mijn EXPLAIN . is) eigenlijk schijnt voor GROUP BY ), maar het toont je 65.

Ik kan het verschil van 25% in uitvoeringstijd niet verklaren, maar het lijkt erop dat de engine in ieder geval een volledige tabel/indexscan uitvoert en het resultaat sorteert voordat het 100 kan overslaan en 30 rijen kan selecteren.

Het beste plan zou waarschijnlijk zijn:

  • Lees rijen van idx_reg_date index (tabel A ) één voor één in aflopende volgorde
  • Kijk of er een overeenkomst is in de idx_order_id index (tabel B )
  • Sla 100 overeenkomende rijen over
  • Stuur 30 overeenkomende rijen
  • Afsluiten

Als er ongeveer 10% rijen in A . zijn die geen overeenkomst hebben in B , zou dit plan zoiets als 143 rijen uit A . lezen .

Het beste wat ik kan doen om dit plan op de een of andere manier te forceren is:

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

Deze query retourneert hetzelfde resultaat in 156 ms (3 keer sneller dan GROUP BY ). Maar dat gaat nog te traag. En hij leest waarschijnlijk nog steeds alle rijen in tabel A .

We kunnen bewijzen dat er een beter plan kan bestaan ​​met een "kleine" subquery-truc:

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

Deze query wordt in "no time" (~ 0 ms) uitgevoerd en geeft hetzelfde resultaat op mijn testgegevens. En hoewel het niet 100% betrouwbaar is, laat het zien dat de optimizer het niet goed doet.

Dus wat zijn mijn conclusies:

  • De optimizer doet niet altijd het beste werk en heeft soms hulp nodig
  • Zelfs als we "het beste plan" kennen, kunnen we het niet altijd afdwingen
  • DISTINCT is niet altijd sneller dan GROUP BY
  • Als er geen index voor alle clausules kan worden gebruikt, wordt het behoorlijk lastig

Testschema en dummygegevens:

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;

Vragen:

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. Inhoud van een PHP-array opslaan in een MySQL-database

  2. Hoe make_time() werkt in PostgreSQL

  3. GROUP_CONCAT met limiet

  4. Kan niet inloggen met aangemaakte gebruiker in mysql