Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Wyraźne a grupowe według

Zwykle zaleca się użycie DISTINCT zamiast GROUP BY , ponieważ tego właśnie chcesz, i pozwól optymalizatorowi wybrać „najlepszy” plan wykonania. Jednak żaden optymalizator nie jest doskonały. Używanie DISTINCT optymalizator może mieć więcej opcji planu wykonania. Ale oznacza to również, że ma więcej opcji wyboru złego planu .

Piszesz, że DISTINCT zapytanie jest „wolne”, ale nie podajesz żadnych liczb. W moim teście (z 10 razy większą liczbą wierszy w MariaDB 10.0.19 i 10.3.13 ) DISTINCT zapytanie jest (tylko) 25% wolniejsze (562ms/453ms). EXPLAIN wynik w ogóle nie pomaga. To nawet „kłamie”. Z LIMIT 100, 30 musiałby przeczytać co najmniej 130 wierszy (to właśnie moje EXPLAIN faktycznie pokazuje dla GROUP BY ), ale pokazuje 65.

Nie potrafię wyjaśnić 25% różnicy w czasie wykonania, ale wygląda na to, że silnik i tak wykonuje pełne skanowanie tabeli/indeksu i sortuje wynik, zanim będzie mógł pominąć 100 i wybrać 30 wierszy.

Najlepszym planem byłoby prawdopodobnie:

  • Odczytaj wiersze z idx_reg_date indeks (tabela A ) jeden po drugim w kolejności malejącej
  • Sprawdź, czy istnieje dopasowanie w idx_order_id indeks (tabela B )
  • Pomiń 100 pasujących wierszy
  • Wyślij 30 pasujących wierszy
  • Wyjdź

Jeśli jest około 10% wierszy w A które nie pasują do B , ten plan odczytałby mniej więcej 143 wiersze z A .

Najlepsze, co mógłbym zrobić, aby jakoś wymusić ten plan, to:

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

To zapytanie zwraca ten sam wynik w 156 ms (3 razy szybciej niż GROUP BY ). Ale to wciąż jest zbyt wolne. I prawdopodobnie nadal odczytuje wszystkie wiersze w tabeli A .

Możemy udowodnić, że lepszy plan może istnieć za pomocą „małej” sztuczki z podzapytaniem:

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

To zapytanie jest wykonywane „bez czasu” (~ 0 ms) i zwraca ten sam wynik w moich danych testowych. I chociaż nie jest w 100% niezawodny, pokazuje, że optymalizator nie wykonuje dobrej pracy.

Więc jakie są moje wnioski:

  • Optymalizator nie zawsze wykonuje najlepszą pracę i czasami potrzebuje pomocy
  • Nawet jeśli znamy „najlepszy plan”, nie zawsze możemy go egzekwować
  • DISTINCT nie zawsze jest szybszy niż GROUP BY
  • Kiedy żaden indeks nie może być użyty dla wszystkich klauzul — sprawy stają się dość skomplikowane

Schemat testowy i dane fikcyjne:

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;

Zapytania:

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. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. usuń wiersze z wielu tabel

  2. MySQL Workbench przerywa połączenie podczas bezczynności

  3. MySQL:jak indeksować klauzulę OR

  4. MySQL/MariaDB - uporządkuj według podzapytania wewnątrz

  5. jak zapisać tylko czas, a nie datę w bazie danych poprzez zapytanie sql