sql >> Databasteknik >  >> RDS >> Mysql

Distinkt vs Group By

Det rekommenderas vanligtvis att använda DISTINCT istället för GROUP BY , eftersom det är vad du faktiskt vill, och låt optimeraren välja den "bästa" exekveringsplanen. Men ingen optimerare är perfekt. Använder DISTINCT optimeraren kan ha fler alternativ för en genomförandeplan. Men det betyder också att den har fler alternativ för att välja en dålig plan .

Du skriver att DISTINCT frågan är "långsam", men du berättar inga siffror. I mitt test (med 10 gånger så många rader på MariaDB 10.0.19 och 10.3.13 ) DISTINCT frågan är ungefär (bara) 25 % långsammare (562ms/453ms). EXPLAIN resultatet är ingen hjälp alls. Det är till och med att "ljuga". Med LIMIT 100, 30 den skulle behöva läsa minst 130 rader (det är vad min EXPLAIN läser faktiskt GROUP BY ), men det visar dig 65.

Jag kan inte förklara skillnaden på 25 % i exekveringstid, men det verkar som att motorn gör en fullständig tabell-/indexskanning i alla fall och sorterar resultatet innan den kan hoppa över 100 och välja 30 rader.

Den bästa planen skulle förmodligen vara:

  • Läs rader från idx_reg_date index (tabell A ) en efter en i fallande ordning
  • Titta om det finns en matchning i idx_order_id index (tabell B )
  • Hoppa över 100 matchande rader
  • Skicka 30 matchande rader
  • Avsluta

Om det finns ungefär 10 % av raderna i A som inte har någon matchning i B , skulle den här planen läsa ungefär 143 rader från A .

Det bästa jag kan göra för att på något sätt tvinga fram den här planen är:

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

Den här frågan returnerar samma resultat på 156 ms (3 gånger snabbare än GROUP BY ). Men det går fortfarande för långsamt. Och den läser förmodligen fortfarande alla rader i tabell A .

Vi kan bevisa att en bättre plan kan existera med ett "litet" subquery-trick:

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

Den här frågan körs på "notid" (~ 0 ms) och returnerar samma resultat på mina testdata. Och även om den inte är 100 % tillförlitlig, visar den att optimeraren inte gör ett bra jobb.

Så vad är mina slutsatser:

  • Optimeraren gör inte alltid det bästa jobbet och behöver ibland hjälp
  • Även när vi känner till "den bästa planen" kan vi inte alltid tillämpa den
  • DISTINCT är inte alltid snabbare än GROUP BY
  • När inget index kan användas för alla satser – det börjar bli ganska knepigt

Testa schema och 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;

Frågor:

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. Android SQLite LIKE escape jokertecken

  2. Problem med att bygga cx_Oracle - libclntsh.so.11.1 => hittades inte

  3. Hämta namnet på anropsproceduren eller funktionen i Oracle PL/SQL

  4. Hur man jämför två rader från samma tabell