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

Distinkt vs gruppe efter

Det anbefales normalt at bruge DISTINCT i stedet for GROUP BY , da det er det du faktisk ønsker, og lad optimizeren vælge den "bedste" eksekveringsplan. Dog - ingen optimizer er perfekt. Brug af DISTINCT optimizeren kan have flere muligheder for en eksekveringsplan. Men det betyder også, at den har flere muligheder for at vælge en dårlig plan .

Du skriver, at DISTINCT forespørgslen er "langsom", men du fortæller ingen tal. I min test (med 10 gange så mange rækker på MariaDB 10.0.19 og 10.3.13 ) DISTINCT forespørgslen er ligesom (kun) 25 % langsommere (562ms/453ms). EXPLAIN resultatet er ingen hjælp overhovedet. Det er endda "løgn". Med LIMIT 100, 30 den skal læse mindst 130 rækker (det er hvad min EXPLAIN bruger faktisk GROUP BY ), men det viser dig 65.

Jeg kan ikke forklare forskellen på 25 % i udførelsestid, men det ser ud til, at motoren under alle omstændigheder laver en fuld tabel/indeksscanning og sorterer resultatet, før den kan springe 100 over og vælge 30 rækker.

Den bedste plan ville sandsynligvis være:

  • Læs rækker fra idx_reg_date indeks (tabel A ) én efter én i faldende rækkefølge
  • Se, om der er et match i idx_order_id indeks (tabel B )
  • Spring 100 matchende rækker over
  • Send 30 matchende rækker
  • Afslut

Hvis der er 10 % af rækkerne i A som ikke har nogen match i B , ville denne plan læse noget i retning af 143 rækker fra A .

Det bedste, jeg kunne gøre for at fremtvinge denne plan, er:

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

Denne forespørgsel returnerer det samme resultat på 156 ms (3 gange hurtigere end GROUP BY ). Men det går stadig for langsomt. Og den læser sandsynligvis stadig alle rækker i tabel A .

Vi kan bevise, at en bedre plan kan eksistere med et "lille" underforespørgselstrick:

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

Denne forespørgsel udføres på "no time" (~ 0 ms) og returnerer det samme resultat på mine testdata. Og selvom den ikke er 100 % pålidelig, viser den, at optimeringsværktøjet ikke gør et godt stykke arbejde.

Så hvad er mine konklusioner:

  • Optimeringsværktøjet gør ikke altid det bedste stykke arbejde og har nogle gange brug for hjælp
  • Selv når vi kender "den bedste plan", kan vi ikke altid håndhæve den
  • DISTINCT er ikke altid hurtigere end GROUP BY
  • Når intet indeks kan bruges til alle klausuler - bliver tingene ret vanskelige

Testskema og 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;

Forespørgsler:

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. Sådan tilføjes ikke null-begrænsning til eksisterende kolonne i MySQL

  2. Hvordan henter jeg JSON-data fra MySQL?

  3. Erstat nulls-værdier i sql ved hjælp af select-sætning i mysql?

  4. PHP Sortering af nærmeste koordinater